@tmlmt/cooklang-parser 3.0.0-alpha.12 → 3.0.0-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +228 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -53
- package/dist/index.d.ts +68 -53
- package/dist/index.js +228 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/classes/category_config.ts","../src/classes/product_catalog.ts","../node_modules/.pnpm/human-regex@2.2.0/node_modules/human-regex/src/human-regex.ts","../src/regex.ts","../src/units/definitions.ts","../src/units/conversion.ts","../src/quantities/numeric.ts","../src/units/compatibility.ts","../src/errors.ts","../src/utils/type_guards.ts","../src/quantities/mutations.ts","../src/utils/parser_helpers.ts","../src/classes/section.ts","../src/quantities/alternatives.ts","../src/units/lookup.ts","../src/utils/general.ts","../src/classes/recipe.ts","../src/classes/shopping_list.ts","../src/classes/shopping_cart.ts","../src/utils/render_helpers.ts"],"sourcesContent":["// Classes\n\nimport { CategoryConfig } from \"./classes/category_config\";\nimport { ProductCatalog } from \"./classes/product_catalog\";\nimport { Recipe } from \"./classes/recipe\";\nimport { ShoppingList } from \"./classes/shopping_list\";\nimport {\n ShoppingCart,\n type ShoppingCartOptions,\n type ShoppingCartSummary,\n} from \"./classes/shopping_cart\";\nimport { Section } from \"./classes/section\";\n\nexport {\n CategoryConfig,\n ProductCatalog,\n Recipe,\n ShoppingList,\n ShoppingCart,\n Section,\n};\n\n// Helpers\n\nimport {\n isAlternativeSelected,\n isGroupedItem,\n renderFractionAsVulgar,\n formatNumericValue,\n formatSingleValue,\n formatQuantity,\n formatUnit,\n formatQuantityWithUnit,\n formatExtendedQuantity,\n formatItemQuantity,\n} from \"./utils/render_helpers\";\nimport {\n isAndGroup,\n isSimpleGroup,\n hasAlternatives,\n} from \"./utils/type_guards\";\nimport { convertQuantityToSystem } from \"./quantities/mutations\";\n\nexport {\n isAlternativeSelected,\n isGroupedItem,\n renderFractionAsVulgar,\n formatNumericValue,\n formatSingleValue,\n formatQuantity,\n formatUnit,\n formatQuantityWithUnit,\n formatExtendedQuantity,\n formatItemQuantity,\n isAndGroup,\n isSimpleGroup,\n hasAlternatives,\n convertQuantityToSystem,\n};\n\n// Types\n\nimport type {\n Metadata,\n Ingredient,\n IngredientFlag,\n IngredientExtras,\n FixedValue,\n Range,\n DecimalValue,\n FractionValue,\n TextValue,\n FixedNumericValue,\n Timer,\n TextItem,\n IngredientItem,\n MaybeScalableQuantity,\n IngredientAlternative,\n CookwareItem,\n TimerItem,\n ArbitraryScalable,\n ArbitraryScalableItem,\n StepItem,\n Step,\n Note,\n NoteItem,\n Cookware,\n CookwareFlag,\n CategorizedIngredients,\n AddedRecipe,\n AddedRecipeOptions,\n AddedIngredient,\n RecipeWithFactor,\n RecipeWithServings,\n CategoryIngredient,\n Category,\n ProductOptionBase,\n ProductOptionCore,\n ProductOption,\n ProductSize,\n ProductSelection,\n CartContent,\n ProductMatch,\n CartMatch,\n ProductMisMatch,\n CartMisMatch,\n NoProductMatchErrorCode,\n QuantityBase,\n QuantityWithPlainUnit,\n QuantityWithExtendedUnit,\n QuantityWithUnitDef,\n QuantityWithUnitLike,\n Unit,\n UnitSystem,\n SpecificUnitSystem,\n ToBaseBySystem,\n UnitType,\n UnitDefinition,\n UnitDefinitionLike,\n AndGroup,\n OrGroup,\n Group,\n FlatAndGroup,\n FlatOrGroup,\n FlatGroup,\n MaybeNestedGroup,\n MaybeNestedAndGroup,\n MaybeNestedOrGroup,\n IngredientQuantityGroup,\n IngredientQuantityAndGroup,\n AlternativeIngredientRef,\n RecipeChoices,\n RecipeAlternatives,\n GetIngredientQuantitiesOptions,\n} from \"./types\";\n\nexport {\n ShoppingCartOptions,\n ShoppingCartSummary,\n Metadata,\n Ingredient,\n IngredientFlag,\n IngredientExtras,\n FixedValue,\n Range,\n DecimalValue,\n FractionValue,\n TextValue,\n FixedNumericValue,\n Timer,\n TextItem,\n IngredientItem,\n MaybeScalableQuantity,\n IngredientAlternative,\n CookwareItem,\n TimerItem,\n ArbitraryScalable,\n ArbitraryScalableItem,\n StepItem,\n Step,\n Note,\n NoteItem,\n Cookware,\n CookwareFlag,\n CategorizedIngredients,\n AddedRecipe,\n AddedRecipeOptions,\n AddedIngredient,\n RecipeWithFactor,\n RecipeWithServings,\n CategoryIngredient,\n Category,\n ProductOptionBase,\n ProductOptionCore,\n ProductOption,\n ProductSize,\n ProductSelection,\n CartContent,\n ProductMatch,\n CartMatch,\n ProductMisMatch,\n CartMisMatch,\n NoProductMatchErrorCode,\n QuantityBase,\n QuantityWithPlainUnit,\n QuantityWithExtendedUnit,\n QuantityWithUnitDef,\n QuantityWithUnitLike,\n Unit,\n UnitSystem,\n SpecificUnitSystem,\n ToBaseBySystem,\n UnitType,\n UnitDefinition,\n UnitDefinitionLike,\n AndGroup,\n OrGroup,\n Group,\n FlatAndGroup,\n FlatOrGroup,\n FlatGroup,\n MaybeNestedGroup,\n MaybeNestedAndGroup,\n MaybeNestedOrGroup,\n IngredientQuantityGroup,\n IngredientQuantityAndGroup,\n AlternativeIngredientRef,\n RecipeChoices,\n RecipeAlternatives,\n GetIngredientQuantitiesOptions,\n};\n\n// Errors\n\nimport {\n NoProductCatalogForCartError,\n NoShoppingListForCartError,\n} from \"./errors\";\n\nexport { NoProductCatalogForCartError, NoShoppingListForCartError };\n","import type { Category, CategoryIngredient } from \"../types\";\n\n/**\n * Parser for category configurations specified à-la-cooklang.\n *\n * ## Usage\n *\n * You can either directly provide the category configuration string when creating the instance\n * e.g. `const categoryConfig = new CategoryConfig(<...>)`, or create it first and then pass\n * the category configuration string to the {@link CategoryConfig.parse | parse()} method.\n *\n * The initialized `CategoryConfig` can then be fed to a {@link ShoppingList}\n *\n * @example\n * ```typescript\n * import { CategoryConfig } from @tmlmt/cooklang-parser;\n *\n * const categoryConfigString = `\n * [Dairy]\n * milk\n * butter\n *\n * [Bakery]\n * flour\n * sugar`;\n *\n * const categoryConfig = new CategoryConfig(categoryConfigString);\n * ```\n *\n * @see [Category Configuration](https://cooklang.org/docs/spec/#shopping-lists) section of the cooklang specs\n *\n * @category Classes\n */\nexport class CategoryConfig {\n /**\n * The parsed categories of ingredients.\n */\n categories: Category[] = [];\n\n /**\n * Creates a new CategoryConfig instance.\n * @param config - The category configuration to parse.\n */\n constructor(config?: string) {\n if (config) {\n this.parse(config);\n }\n }\n\n /**\n * Parses a category configuration from a string into property\n * {@link CategoryConfig.categories | categories}\n * @param config - The category configuration to parse.\n */\n parse(config: string) {\n let currentCategory: Category | null = null;\n const categoryNames = new Set<string>();\n const ingredientNames = new Set<string>();\n\n for (const line of config.split(\"\\n\")) {\n const trimmedLine = line.trim();\n\n if (trimmedLine.length === 0) {\n continue;\n }\n\n if (trimmedLine.startsWith(\"[\") && trimmedLine.endsWith(\"]\")) {\n const categoryName = trimmedLine\n .substring(1, trimmedLine.length - 1)\n .trim();\n\n if (categoryNames.has(categoryName)) {\n throw new Error(`Duplicate category found: ${categoryName}`);\n }\n categoryNames.add(categoryName);\n\n currentCategory = { name: categoryName, ingredients: [] };\n this.categories.push(currentCategory);\n } else {\n if (currentCategory === null) {\n throw new Error(\n `Ingredient found without a category: ${trimmedLine}`,\n );\n }\n\n const aliases = trimmedLine.split(\"|\").map((s) => s.trim());\n for (const alias of aliases) {\n if (ingredientNames.has(alias)) {\n throw new Error(`Duplicate ingredient/alias found: ${alias}`);\n }\n ingredientNames.add(alias);\n }\n\n const ingredient: CategoryIngredient = {\n name: aliases[0]!, // We know this exists because trimmedLine is not empty\n aliases: aliases,\n };\n currentCategory.ingredients.push(ingredient);\n }\n }\n }\n}\n","import TOML from \"smol-toml\";\nimport type {\n FixedNumericValue,\n ProductOption,\n ProductOptionToml,\n ProductSize,\n} from \"../types\";\nimport type { TomlTable } from \"smol-toml\";\nimport {\n isPositiveIntegerString,\n parseQuantityInput,\n stringifyQuantityValue,\n} from \"../utils/parser_helpers\";\nimport { InvalidProductCatalogFormat } from \"../errors\";\n\n/**\n * Product Catalog Manager: used in conjunction with {@link ShoppingCart}\n *\n * ## Usage\n *\n * You can either directly populate the products by feeding the {@link ProductCatalog.products | products} property. Alternatively,\n * you can provide a catalog in TOML format to either the constructor itself or to the {@link ProductCatalog.parse | parse()} method.\n *\n * @category Classes\n *\n * @example\n * ```typescript\n * import { ProductCatalog } from \"@tmlmt/cooklang-parser\";\n *\n * const catalog = `\n * [eggs]\n * aliases = [\"oeuf\", \"huevo\"]\n * 01123 = { name = \"Single Egg\", size = \"1\", price = 2 }\n * 11244 = { name = \"Pack of 6 eggs\", size = \"6\", price = 10 }\n *\n * [flour]\n * aliases = [\"farine\", \"Mehl\"]\n * 01124 = { name = \"Small pack\", size = \"100%g\", price = 1.5 }\n * 14141 = { name = \"Big pack\", size = \"6%kg\", price = 10 }\n * `\n * const catalog = new ProductCatalog(catalog);\n * const eggs = catalog.find(\"oeuf\");\n * ```\n */\nexport class ProductCatalog {\n public products: ProductOption[] = [];\n\n constructor(tomlContent?: string) {\n if (tomlContent) this.parse(tomlContent);\n }\n\n /**\n * Parses a TOML string into a list of product options.\n * @param tomlContent - The TOML string to parse.\n * @returns A parsed list of `ProductOption`.\n */\n public parse(tomlContent: string): ProductOption[] {\n const catalogRaw = TOML.parse(tomlContent);\n\n // Reset internal state\n this.products = [];\n\n if (!this.isValidTomlContent(catalogRaw)) {\n throw new InvalidProductCatalogFormat();\n }\n\n for (const [ingredientName, ingredientData] of Object.entries(catalogRaw)) {\n const ingredientTable = ingredientData as TomlTable;\n const aliases = ingredientTable.aliases as string[] | undefined;\n\n for (const [key, productData] of Object.entries(ingredientTable)) {\n if (key === \"aliases\") {\n continue;\n }\n\n const productId = key;\n const { name, size, price, ...rest } =\n productData as unknown as ProductOptionToml;\n\n // Handle size as string or string[]\n const sizeStrings = Array.isArray(size) ? size : [size];\n const sizes: ProductSize[] = sizeStrings.map((sizeStr) => {\n const sizeAndUnitRaw = sizeStr.split(\"%\");\n const sizeParsed = parseQuantityInput(\n sizeAndUnitRaw[0]!,\n ) as FixedNumericValue;\n const productSize: ProductSize = { size: sizeParsed };\n if (sizeAndUnitRaw.length > 1) {\n productSize.unit = sizeAndUnitRaw[1]!;\n }\n return productSize;\n });\n\n const productOption: ProductOption = {\n id: productId,\n productName: name,\n ingredientName: ingredientName,\n price: price,\n sizes,\n ...rest,\n };\n if (aliases) {\n productOption.ingredientAliases = aliases;\n }\n\n this.products.push(productOption);\n }\n }\n\n return this.products;\n }\n\n /**\n * Stringifies the catalog to a TOML string.\n * @returns The TOML string representation of the catalog.\n */\n public stringify(): string {\n const grouped: Record<string, TomlTable> = {};\n\n for (const product of this.products) {\n const {\n id,\n ingredientName,\n ingredientAliases,\n sizes,\n productName,\n ...rest\n } = product;\n if (!grouped[ingredientName]) {\n grouped[ingredientName] = {};\n }\n if (ingredientAliases && !grouped[ingredientName].aliases) {\n grouped[ingredientName].aliases = ingredientAliases;\n }\n\n // Stringify each size as \"value%unit\" or just \"value\"\n const sizeStrings = sizes.map((s) =>\n s.unit\n ? `${stringifyQuantityValue(s.size)}%${s.unit}`\n : stringifyQuantityValue(s.size),\n );\n\n grouped[ingredientName][id] = {\n ...rest,\n name: productName,\n // Use array if multiple sizes, otherwise single string\n size: sizeStrings.length === 1 ? sizeStrings[0]! : sizeStrings,\n };\n }\n\n return TOML.stringify(grouped);\n }\n\n /**\n * Adds a product to the catalog.\n * @param productOption - The product to add.\n */\n public add(productOption: ProductOption): void {\n this.products.push(productOption);\n }\n\n /**\n * Removes a product from the catalog by its ID.\n * @param productId - The ID of the product to remove.\n */\n public remove(productId: string): void {\n this.products = this.products.filter((product) => product.id !== productId);\n }\n\n private isValidTomlContent(catalog: TomlTable): boolean {\n for (const productsRaw of Object.values(catalog)) {\n if (typeof productsRaw !== \"object\" || productsRaw === null) {\n return false;\n }\n\n for (const [id, obj] of Object.entries(productsRaw)) {\n if (id === \"aliases\") {\n if (!Array.isArray(obj)) {\n return false;\n }\n } else {\n if (!isPositiveIntegerString(id)) {\n return false;\n }\n if (typeof obj !== \"object\" || obj === null) {\n return false;\n }\n\n const record = obj as Record<string, unknown>;\n const keys = Object.keys(record);\n\n const mandatoryKeys = [\"name\", \"size\", \"price\"];\n\n if (mandatoryKeys.some((key) => !keys.includes(key))) {\n return false;\n }\n\n const hasProductName = typeof record.name === \"string\";\n // Size can be a string or an array of strings\n const hasSize =\n typeof record.size === \"string\" ||\n (Array.isArray(record.size) &&\n record.size.every((s) => typeof s === \"string\"));\n const hasPrice = typeof record.price === \"number\";\n\n if (!(hasProductName && hasSize && hasPrice)) {\n return false;\n }\n }\n }\n }\n\n return true;\n }\n}\n","type PartialBut<T, K extends keyof T> = Partial<T> & Pick<T, K>;\n\nconst escapeCache = new Map<string, string>();\n\nconst Flags = {\n GLOBAL: \"g\",\n NON_SENSITIVE: \"i\",\n MULTILINE: \"m\",\n DOT_ALL: \"s\",\n UNICODE: \"u\",\n STICKY: \"y\",\n} as const;\n\nconst Ranges = Object.freeze({\n digit: \"0-9\",\n lowercaseLetter: \"a-z\",\n uppercaseLetter: \"A-Z\",\n letter: \"a-zA-Z\",\n alphanumeric: \"a-zA-Z0-9\",\n anyCharacter: \".\",\n});\n\ntype RangeKeys = keyof typeof Ranges;\n\nconst Quantifiers = Object.freeze({\n zeroOrMore: \"*\",\n oneOrMore: \"+\",\n optional: \"?\",\n});\n\ntype Quantifiers =\n | \"exactly\"\n | \"atLeast\"\n | \"atMost\"\n | \"between\"\n | \"oneOrMore\"\n | \"zeroOrMore\"\n | \"repeat\";\ntype QuantifierMethods = Quantifiers | \"optional\" | \"lazy\";\n\ntype WithLazy = HumanRegex;\ntype Base = Omit<HumanRegex, \"lazy\">;\ntype AtStart = Omit<Base, QuantifierMethods | \"endGroup\">;\ntype AfterAnchor = Omit<Base, QuantifierMethods | \"or\">;\ntype SimpleQuantifier = Omit<Base, Quantifiers>;\ntype LazyQuantifier = Omit<WithLazy, Quantifiers>;\n\nclass HumanRegex {\n private parts: string[];\n private flags: Set<string>;\n\n constructor() {\n this.parts = [];\n this.flags = new Set<string>();\n }\n\n digit(): Base {\n return this.add(\"\\\\d\");\n }\n\n special(): Base {\n return this.add(\"(?=.*[!@#$%^&*])\");\n }\n\n word(): Base {\n return this.add(\"\\\\w\");\n }\n\n whitespace(): Base {\n return this.add(\"\\\\s\");\n }\n\n nonWhitespace(): Base {\n return this.add(\"\\\\S\");\n }\n\n literal(text: string): this {\n return this.add(escapeLiteral(text));\n }\n\n or(): AfterAnchor {\n return this.add(\"|\");\n }\n\n range(name: RangeKeys): Base {\n const range = Ranges[name];\n if (!range) throw new Error(`Unknown range: ${name}`);\n return this.add(`[${range}]`);\n }\n\n notRange(name: RangeKeys): Base {\n const range = Ranges[name];\n if (!range) throw new Error(`Unknown range: ${name}`);\n return this.add(`[^${range}]`);\n }\n\n anyOf(chars: string): Base {\n return this.add(`[${chars}]`);\n }\n\n notAnyOf(chars: string): Base {\n return this.add(`[^${chars}]`);\n }\n\n lazy(): Base {\n const lastPart = this.parts.pop();\n if (!lastPart) throw new Error(\"No quantifier to make lazy\");\n return this.add(`${lastPart}?`);\n }\n\n letter(): Base {\n return this.add(\"[a-zA-Z]\");\n }\n\n anyCharacter(): Base {\n return this.add(\".\");\n }\n\n newline(): Base {\n return this.add(\"(\\\\r\\\\n|\\\\r|\\\\n)\"); // Windows: \\r\\n, Unix: \\n, Old Macs: \\r\n }\n\n negativeLookahead(pattern: string): Base {\n return this.add(`(?!${pattern})`);\n }\n\n positiveLookahead(pattern: string): Base {\n return this.add(`(?=${pattern})`);\n }\n\n positiveLookbehind(pattern: string): Base {\n return this.add(`(?<=${pattern})`);\n }\n\n negativeLookbehind(pattern: string): Base {\n return this.add(`(?<!${pattern})`);\n }\n\n hasSpecialCharacter(): Base {\n return this.add(\"(?=.*[!@#$%^&*])\");\n }\n\n hasDigit(): Base {\n return this.add(\"(?=.*\\\\d)\");\n }\n\n hasLetter(): Base {\n return this.add(\"(?=.*[a-zA-Z])\");\n }\n\n optional(): SimpleQuantifier {\n return this.add(Quantifiers.optional);\n }\n\n exactly(n: number): SimpleQuantifier {\n return this.add(`{${n}}`);\n }\n\n atLeast(n: number): LazyQuantifier {\n return this.add(`{${n},}`);\n }\n\n atMost(n: number): LazyQuantifier {\n return this.add(`{0,${n}}`);\n }\n\n between(min: number, max: number): LazyQuantifier {\n return this.add(`{${min},${max}}`);\n }\n\n oneOrMore(): LazyQuantifier {\n return this.add(Quantifiers.oneOrMore);\n }\n\n zeroOrMore(): LazyQuantifier {\n return this.add(Quantifiers.zeroOrMore);\n }\n\n startNamedGroup(name: string): AfterAnchor {\n return this.add(`(?<${name}>`);\n }\n\n startGroup(): AfterAnchor {\n return this.add(\"(?:\");\n }\n\n startCaptureGroup(): AfterAnchor {\n return this.add(\"(\");\n }\n\n wordBoundary(): Base {\n return this.add(\"\\\\b\");\n }\n\n nonWordBoundary(): Base {\n return this.add(\"\\\\B\");\n }\n\n endGroup(): Base {\n return this.add(\")\");\n }\n\n startAnchor(): AfterAnchor {\n return this.add(\"^\");\n }\n\n endAnchor(): AfterAnchor {\n return this.add(\"$\");\n }\n\n global(): this {\n this.flags.add(Flags.GLOBAL);\n return this;\n }\n\n nonSensitive(): this {\n this.flags.add(Flags.NON_SENSITIVE);\n return this;\n }\n\n multiline(): this {\n this.flags.add(Flags.MULTILINE);\n return this;\n }\n\n dotAll(): this {\n this.flags.add(Flags.DOT_ALL);\n return this;\n }\n\n sticky(): this {\n this.flags.add(Flags.STICKY);\n return this;\n }\n\n unicodeChar(variant?: \"u\" | \"l\" | \"t\" | \"m\" | \"o\"): Base {\n this.flags.add(Flags.UNICODE);\n const validVariants = new Set([\"u\", \"l\", \"t\", \"m\", \"o\"] as const);\n\n if (variant !== undefined && !validVariants.has(variant)) {\n throw new Error(`Invalid Unicode letter variant: ${variant}`);\n }\n\n return this.add(`\\\\p{L${variant ?? \"\"}}`);\n }\n\n unicodeDigit(): Base {\n this.flags.add(Flags.UNICODE);\n return this.add(\"\\\\p{N}\");\n }\n\n unicodePunctuation(): Base {\n this.flags.add(Flags.UNICODE);\n return this.add(\"\\\\p{P}\");\n }\n\n unicodeSymbol(): Base {\n this.flags.add(Flags.UNICODE);\n return this.add(\"\\\\p{S}\");\n }\n\n repeat(count: number): Base {\n if (this.parts.length === 0) {\n throw new Error(\"No pattern to repeat\");\n }\n\n const lastPart = this.parts.pop();\n this.parts.push(`(${lastPart}){${count}}`);\n return this;\n }\n\n ipv4Octet(): Base {\n return this.add(\"(25[0-5]|2[0-4]\\\\d|1\\\\d\\\\d|[1-9]\\\\d|\\\\d)\");\n }\n\n protocol(): Base {\n return this.add(\"https?://\");\n }\n\n www(): Base {\n return this.add(\"(www\\\\.)?\");\n }\n\n tld(): Base {\n return this.add(\"(com|org|net)\");\n }\n\n path(): Base {\n return this.add(\"(/\\\\w+)*\");\n }\n\n private add(part: string): this {\n this.parts.push(part);\n return this;\n }\n\n toString(): string {\n return this.parts.join(\"\");\n }\n\n toRegExp(): RegExp {\n return new RegExp(this.toString(), [...this.flags].join(\"\"));\n }\n}\n\nfunction escapeLiteral(text: string): string {\n if (!escapeCache.has(text)) {\n escapeCache.set(text, text.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\"));\n }\n return escapeCache.get(text)!;\n}\n\nconst createRegex = (): AtStart => new HumanRegex();\n\nconst Patterns = (() => {\n const createCachedPattern = (\n builder: () => PartialBut<HumanRegex, \"toRegExp\">\n ) => {\n const regex = builder().toRegExp();\n return () => new RegExp(regex.source, regex.flags);\n };\n\n return {\n email: createCachedPattern(() =>\n createRegex()\n .startAnchor()\n .word()\n .oneOrMore()\n .literal(\"@\")\n .word()\n .oneOrMore()\n .startGroup()\n .literal(\".\")\n .word()\n .oneOrMore()\n .endGroup()\n .zeroOrMore()\n .literal(\".\")\n .letter()\n .atLeast(2)\n .endAnchor()\n ),\n url: createCachedPattern(() =>\n createRegex()\n .startAnchor()\n .protocol()\n .www()\n .word()\n .oneOrMore()\n .literal(\".\")\n .tld()\n .path()\n .endAnchor()\n ),\n phoneInternational: createCachedPattern(() =>\n createRegex()\n .startAnchor()\n .literal(\"+\")\n .digit()\n .between(1, 3)\n .literal(\"-\")\n .digit()\n .between(3, 14)\n .endAnchor()\n ),\n };\n})();\n\nexport { createRegex, Patterns, Flags, Ranges, Quantifiers };\n","import { createRegex } from \"human-regex\";\n\nexport const metadataRegex = createRegex()\n .literal(\"---\").newline()\n .startCaptureGroup().anyCharacter().zeroOrMore().optional().endGroup()\n .newline().literal(\"---\")\n .dotAll().toRegExp();\n\nexport const scalingMetaValueRegex = (varName: string): RegExp => createRegex()\n .startAnchor()\n .literal(varName)\n .literal(\":\")\n .anyOf(\"\\\\t \").zeroOrMore()\n .startCaptureGroup()\n .startCaptureGroup()\n .notAnyOf(\",\\\\n\").oneOrMore()\n .endGroup()\n .startGroup()\n .literal(\",\")\n .whitespace().zeroOrMore()\n .startCaptureGroup()\n .anyCharacter().oneOrMore()\n .endGroup()\n .endGroup().optional()\n .endGroup()\n .endAnchor()\n .multiline()\n .toRegExp()\n\nconst nonWordChar = \"\\\\s@#~\\\\[\\\\]{(,;:!?\"\nconst nonWordCharStrict = \"\\\\s@#~\\\\[\\\\]{(,;:!?|\"\n\nexport const ingredientWithAlternativeRegex = createRegex()\n .literal(\"@\")\n .startNamedGroup(\"ingredientModifiers\")\n .anyOf(\"@\\\\-&?\").zeroOrMore()\n .endGroup().optional()\n .startNamedGroup(\"ingredientRecipeAnchor\")\n .literal(\"./\")\n .endGroup().optional()\n .startGroup()\n .startGroup()\n .startNamedGroup(\"mIngredientName\")\n .notAnyOf(nonWordChar).oneOrMore()\n .startGroup()\n .whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore()\n .endGroup().oneOrMore()\n .endGroup()\n .positiveLookahead(\"\\\\s*(?:\\\\{[^\\\\}]*\\\\}|\\\\([^)]*\\\\))\")\n .endGroup()\n .or()\n .startNamedGroup(\"sIngredientName\")\n .notAnyOf(nonWordChar).zeroOrMore()\n .notAnyOf(\"\\\\.\"+nonWordChar)\n .endGroup()\n .endGroup()\n .startGroup()\n .literal(\"{\")\n .startNamedGroup(\"ingredientQuantityModifier\")\n .literal(\"=\").exactly(1)\n .endGroup().optional()\n .startNamedGroup(\"ingredientQuantity\")\n .startGroup()\n .notAnyOf(\"}|%\").oneOrMore()\n .endGroup().optional()\n .startGroup()\n .literal(\"%\")\n .notAnyOf(\"|}\").oneOrMore().lazy()\n .endGroup().optional()\n .startGroup()\n .literal(\"|\")\n .notAnyOf(\"}\").oneOrMore().lazy()\n .endGroup().zeroOrMore()\n .endGroup()\n .literal(\"}\")\n .endGroup().optional()\n .startGroup()\n .literal(\"(\")\n .startNamedGroup(\"ingredientPreparation\")\n .notAnyOf(\")\").oneOrMore().lazy()\n .endGroup()\n .literal(\")\")\n .endGroup().optional()\n .startGroup()\n .literal(\"[\")\n .startNamedGroup(\"ingredientNote\")\n .notAnyOf(\"\\\\]\").oneOrMore().lazy()\n .endGroup()\n .literal(\"]\")\n .endGroup().optional()\n .startNamedGroup(\"ingredientAlternative\")\n .startGroup()\n .literal(\"|\")\n .startGroup()\n .anyOf(\"@\\\\-&?\").zeroOrMore()\n .endGroup().optional()\n .startGroup()\n .literal(\"./\")\n .endGroup().optional()\n .startGroup()\n .startGroup()\n .startGroup()\n .notAnyOf(nonWordChar).oneOrMore()\n .startGroup()\n .whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore()\n .endGroup().oneOrMore()\n .endGroup()\n .positiveLookahead(\"\\\\s*(?:\\\\{[^\\\\}]*\\\\}|\\\\([^)]*\\\\))\")\n .endGroup()\n .or()\n .startGroup()\n .notAnyOf(nonWordChar).oneOrMore()\n .endGroup()\n .endGroup()\n .startGroup()\n .literal(\"{\")\n .startGroup()\n .literal(\"=\").exactly(1)\n .endGroup().optional()\n .startGroup()\n .notAnyOf(\"}%\").oneOrMore()\n .endGroup().optional()\n .startGroup()\n .literal(\"%\")\n .startGroup()\n .notAnyOf(\"}\").oneOrMore().lazy()\n .endGroup()\n .endGroup().optional()\n .literal(\"}\")\n .endGroup().optional()\n .startGroup()\n .literal(\"(\")\n .startGroup()\n .notAnyOf(\")\").oneOrMore().lazy()\n .endGroup()\n .literal(\")\")\n .endGroup().optional()\n .startGroup()\n .literal(\"[\")\n .startGroup()\n .notAnyOf(\"\\\\]\").oneOrMore().lazy()\n .endGroup()\n .literal(\"]\")\n .endGroup().optional()\n .endGroup().zeroOrMore()\n .endGroup()\n .toRegExp();\n\nexport const inlineIngredientAlternativesRegex = new RegExp(\"\\\\|\" + ingredientWithAlternativeRegex.source.slice(1))\n\nexport const quantityAlternativeRegex = createRegex()\n .startNamedGroup(\"quantity\")\n .notAnyOf(\"}|%\").oneOrMore()\n .endGroup().optional()\n .startGroup()\n .literal(\"%\")\n .startNamedGroup(\"unit\")\n .notAnyOf(\"|}\").oneOrMore()\n .endGroup()\n .endGroup().optional()\n .startGroup()\n .literal(\"|\")\n .startNamedGroup(\"alternative\")\n .startGroup()\n .notAnyOf(\"}\").oneOrMore()\n .endGroup().zeroOrMore()\n .endGroup()\n .endGroup().optional()\n .toRegExp()\n \nexport const ingredientWithGroupKeyRegex = createRegex()\n .literal(\"@|\")\n .startNamedGroup(\"gIngredientGroupKey\")\n .notAnyOf(nonWordCharStrict).oneOrMore()\n .endGroup()\n .literal(\"|\")\n .startNamedGroup(\"gIngredientModifiers\")\n .anyOf(\"@\\\\-&?\").zeroOrMore()\n .endGroup().optional()\n .startNamedGroup(\"gIngredientRecipeAnchor\")\n .literal(\"./\")\n .endGroup().optional()\n .startGroup()\n .startGroup()\n .startNamedGroup(\"gmIngredientName\")\n .notAnyOf(nonWordChar).oneOrMore()\n .startGroup()\n .whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore()\n .endGroup().oneOrMore()\n .endGroup()\n .positiveLookahead(\"\\\\s*(?:\\\\{[^\\\\}]*\\\\}|\\\\([^)]*\\\\))\")\n .endGroup()\n .or()\n .startNamedGroup(\"gsIngredientName\")\n .notAnyOf(nonWordChar).zeroOrMore()\n .notAnyOf(\"\\\\.\"+nonWordChar)\n .endGroup()\n .endGroup()\n .startGroup()\n .literal(\"{\")\n .startNamedGroup(\"gIngredientQuantityModifier\")\n .literal(\"=\").exactly(1)\n .endGroup().optional()\n .startNamedGroup(\"gIngredientQuantity\")\n .startGroup()\n .notAnyOf(\"}|%\").oneOrMore()\n .endGroup().optional()\n .startGroup()\n .literal(\"%\")\n .notAnyOf(\"|}\").oneOrMore().lazy()\n .endGroup().optional()\n .startGroup()\n .literal(\"|\")\n .notAnyOf(\"}\").oneOrMore().lazy()\n .endGroup().zeroOrMore()\n .endGroup()\n .literal(\"}\")\n .endGroup().optional()\n .startGroup()\n .literal(\"(\")\n .startNamedGroup(\"gIngredientPreparation\")\n .notAnyOf(\")\").oneOrMore().lazy()\n .endGroup()\n .literal(\")\")\n .endGroup().optional()\n .toRegExp()\n\nexport const ingredientAliasRegex = createRegex()\n .startAnchor()\n .startNamedGroup(\"ingredientListName\")\n .notAnyOf(\"|\").oneOrMore()\n .endGroup()\n .literal(\"|\")\n .startNamedGroup(\"ingredientDisplayName\")\n .notAnyOf(\"|\").oneOrMore()\n .endGroup()\n .endAnchor()\n .toRegExp();\n\nexport const cookwareRegex = createRegex()\n .literal(\"#\")\n .startNamedGroup(\"cookwareModifiers\")\n .anyOf(\"\\\\-&?\").zeroOrMore()\n .endGroup()\n .startGroup()\n .startGroup()\n .startNamedGroup(\"mCookwareName\")\n .notAnyOf(nonWordChar).oneOrMore()\n .startGroup()\n .whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore()\n .endGroup().oneOrMore()\n .endGroup().positiveLookahead(\"\\\\s*(?:\\\\{[^\\\\}]*\\\\})\")\n .endGroup()\n .or()\n .startNamedGroup(\"sCookwareName\")\n .notAnyOf(nonWordChar).zeroOrMore()\n .notAnyOf(\"\\\\.\"+nonWordChar)\n .endGroup()\n .endGroup()\n .startGroup()\n .literal(\"{\")\n .startNamedGroup(\"cookwareQuantity\")\n .anyCharacter().zeroOrMore().lazy()\n .endGroup()\n .literal(\"}\")\n .endGroup().optional()\n .toRegExp();\n\nconst timerRegex = createRegex()\n .literal(\"~\")\n .startNamedGroup(\"timerName\")\n .anyCharacter().zeroOrMore().lazy()\n .endGroup()\n .literal(\"{\")\n .startNamedGroup(\"timerQuantity\")\n .anyCharacter().oneOrMore().lazy()\n .endGroup()\n .startGroup()\n .literal(\"%\")\n .startNamedGroup(\"timerUnit\")\n .anyCharacter().oneOrMore().lazy()\n .endGroup()\n .endGroup().optional()\n .literal(\"}\")\n .toRegExp()\n\nexport const arbitraryScalableRegex = createRegex()\n .literal(\"{{\")\n .startGroup()\n .startNamedGroup(\"arbitraryName\")\n .notAnyOf(\"}:%\").oneOrMore()\n .endGroup()\n .literal(\":\")\n .endGroup().optional()\n .startNamedGroup(\"arbitraryQuantity\")\n .startGroup()\n .notAnyOf(\"}|%\").oneOrMore()\n .endGroup().optional()\n .startGroup()\n .literal(\"%\")\n .notAnyOf(\"|}\").oneOrMore().lazy()\n .endGroup().optional()\n .startGroup()\n .literal(\"|\")\n .notAnyOf(\"}\").oneOrMore().lazy()\n .endGroup().zeroOrMore()\n .endGroup()\n .literal(\"}}\")\n .toRegExp();\n\nexport const tokensRegex = new RegExp(\n [\n ingredientWithGroupKeyRegex,\n ingredientWithAlternativeRegex,\n cookwareRegex,\n timerRegex,\n arbitraryScalableRegex\n ]\n .map((r) => r.source)\n .join(\"|\"),\n \"gu\",\n);\n\nexport const commentRegex = createRegex()\n .literal(\"--\")\n .anyCharacter().zeroOrMore()\n .global()\n .toRegExp();\n\nexport const blockCommentRegex = createRegex()\n .literal(\"[-\")\n .anyCharacter().zeroOrMore().lazy()\n .literal(\"-]\")\n .whitespace().zeroOrMore()\n .global()\n .toRegExp();\n\nexport const shoppingListRegex = createRegex()\n .literal(\"[\")\n .startNamedGroup(\"name\")\n .anyCharacter().oneOrMore()\n .endGroup()\n .literal(\"]\")\n .newline()\n .startNamedGroup(\"items\")\n .anyCharacter().zeroOrMore().lazy()\n .endGroup()\n .startGroup()\n .newline().newline()\n .or()\n .endAnchor()\n .endGroup()\n .global()\n .toRegExp()\n\nexport const rangeRegex = createRegex()\n .startAnchor()\n .digit().oneOrMore()\n .startGroup()\n .anyOf(\".,/\").exactly(1)\n .digit().oneOrMore()\n .endGroup().optional()\n .literal(\"-\")\n .digit().oneOrMore()\n .startGroup()\n .anyOf(\".,/\").exactly(1)\n .digit().oneOrMore()\n .endGroup().optional()\n .endAnchor()\n .toRegExp()\n\nexport const numberLikeRegex = createRegex()\n .startAnchor()\n .digit().oneOrMore()\n .startGroup()\n .anyOf(\".,/\").exactly(1)\n .digit().oneOrMore()\n .endGroup().optional()\n .endAnchor()\n .toRegExp()\n\nexport const floatRegex = createRegex()\n .startAnchor()\n .digit().oneOrMore()\n .startGroup()\n .anyOf(\".\").exactly(1)\n .digit().oneOrMore()\n .endGroup().optional()\n .endAnchor()\n .toRegExp()","import type { UnitDefinition, UnitDefinitionLike } from \"../types\";\n\n// Base units: mass -> gram (g), volume -> milliliter (ml)\nexport const units: UnitDefinition[] = [\n // Mass (Metric)\n {\n name: \"g\",\n type: \"mass\",\n system: \"metric\",\n aliases: [\"gram\", \"grams\", \"grammes\"],\n toBase: 1,\n maxValue: 999,\n },\n {\n name: \"kg\",\n type: \"mass\",\n system: \"metric\",\n aliases: [\"kilogram\", \"kilograms\", \"kilogrammes\", \"kilos\", \"kilo\"],\n toBase: 1000,\n },\n // Mass (US/UK - identical in both systems)\n {\n name: \"oz\",\n type: \"mass\",\n system: \"ambiguous\",\n aliases: [\"ounce\", \"ounces\"],\n toBase: 28.3495, // default: US (same as UK)\n toBaseBySystem: { US: 28.3495, UK: 28.3495 },\n maxValue: 31, // 16 oz = 1 lb, allow a bit more\n fractions: { enabled: true, denominators: [2] },\n },\n {\n name: \"lb\",\n type: \"mass\",\n system: \"ambiguous\",\n aliases: [\"pound\", \"pounds\"],\n toBase: 453.592, // default: US (same as UK)\n toBaseBySystem: { US: 453.592, UK: 453.592 },\n fractions: { enabled: true, denominators: [2, 4] },\n },\n\n // Volume (Metric)\n {\n name: \"ml\",\n type: \"volume\",\n system: \"metric\",\n aliases: [\"milliliter\", \"milliliters\", \"millilitre\", \"millilitres\", \"cc\"],\n toBase: 1,\n maxValue: 999,\n },\n {\n name: \"cl\",\n type: \"volume\",\n system: \"metric\",\n aliases: [\"centiliter\", \"centiliters\", \"centilitre\", \"centilitres\"],\n toBase: 10,\n isBestUnit: false, // exists but not a \"best\" candidate\n },\n {\n name: \"dl\",\n type: \"volume\",\n system: \"metric\",\n aliases: [\"deciliter\", \"deciliters\", \"decilitre\", \"decilitres\"],\n toBase: 100,\n isBestUnit: false, // exists but not a \"best\" candidate\n },\n {\n name: \"l\",\n type: \"volume\",\n system: \"metric\",\n aliases: [\"liter\", \"liters\", \"litre\", \"litres\"],\n toBase: 1000,\n },\n\n // Volume (JP)\n {\n name: \"go\",\n type: \"volume\",\n system: \"JP\",\n aliases: [\"gou\", \"goo\", \"合\", \"rice cup\"],\n toBase: 180,\n maxValue: 10,\n },\n\n // Volume (Ambiguous: metric/US/UK)\n {\n name: \"tsp\",\n type: \"volume\",\n system: \"ambiguous\",\n aliases: [\"teaspoon\", \"teaspoons\"],\n toBase: 5, // default: metric\n toBaseBySystem: { metric: 5, US: 4.929, UK: 5.919, JP: 5 },\n maxValue: 5, // 3 tsp = 1 tbsp (but allow a bit more)\n fractions: { enabled: true, denominators: [2, 3, 4, 8] },\n },\n {\n name: \"tbsp\",\n type: \"volume\",\n system: \"ambiguous\",\n aliases: [\"tablespoon\", \"tablespoons\"],\n toBase: 15, // default: metric\n toBaseBySystem: { metric: 15, US: 14.787, UK: 17.758, JP: 15 },\n maxValue: 4, // ~16 tbsp = 1 cup\n fractions: { enabled: true },\n },\n\n // Volume (Ambiguous: US/UK only)\n {\n name: \"fl-oz\",\n type: \"volume\",\n system: \"ambiguous\",\n aliases: [\"fluid ounce\", \"fluid ounces\"],\n toBase: 29.5735, // default: US\n toBaseBySystem: { US: 29.5735, UK: 28.4131 },\n maxValue: 15, // 8 fl-oz ~ 1 cup, allow more\n fractions: { enabled: true, denominators: [2] },\n },\n {\n name: \"cup\",\n type: \"volume\",\n system: \"ambiguous\",\n aliases: [\"cups\"],\n toBase: 236.588, // default: US\n toBaseBySystem: { US: 236.588, UK: 284.131 },\n maxValue: 15, // upgrade to gallons above 15 cups\n fractions: { enabled: true },\n },\n {\n name: \"pint\",\n type: \"volume\",\n system: \"ambiguous\",\n aliases: [\"pints\"],\n toBase: 473.176, // default: US\n toBaseBySystem: { US: 473.176, UK: 568.261 },\n maxValue: 3, // 2 pints = 1 quart\n fractions: { enabled: true, denominators: [2] },\n isBestUnit: false, // exists but not a \"best\" candidate\n },\n {\n name: \"quart\",\n type: \"volume\",\n system: \"ambiguous\",\n aliases: [\"quarts\"],\n toBase: 946.353, // default: US\n toBaseBySystem: { US: 946.353, UK: 1136.52 },\n maxValue: 3, // 4 quarts = 1 gallon\n fractions: { enabled: true, denominators: [2] },\n isBestUnit: false, // exists but not a \"best\" candidate\n },\n {\n name: \"gallon\",\n type: \"volume\",\n system: \"ambiguous\",\n aliases: [\"gallons\"],\n toBase: 3785.41, // default: US\n toBaseBySystem: { US: 3785.41, UK: 4546.09 },\n fractions: { enabled: true, denominators: [2] },\n },\n\n // Count units (no conversion, but recognized as a type)\n {\n name: \"piece\",\n type: \"count\",\n system: \"metric\",\n aliases: [\"pieces\", \"pc\"],\n toBase: 1,\n maxValue: 999,\n },\n];\n\nconst unitMap = new Map<string, UnitDefinition>();\nfor (const unit of units) {\n unitMap.set(unit.name.toLowerCase(), unit);\n for (const alias of unit.aliases) {\n unitMap.set(alias.toLowerCase(), unit);\n }\n}\n\nexport function normalizeUnit(unit: string = \"\"): UnitDefinition | undefined {\n return unitMap.get(unit.toLowerCase().trim());\n}\n\nexport const NO_UNIT = \"__no-unit__\";\n\nexport function resolveUnit(\n name: string = NO_UNIT,\n integerProtected: boolean = false,\n): UnitDefinitionLike {\n const normalizedUnit = normalizeUnit(name);\n const resolvedUnit: UnitDefinitionLike = normalizedUnit\n ? { ...normalizedUnit, name }\n : { name, type: \"other\", system: \"none\" };\n return integerProtected\n ? { ...resolvedUnit, integerProtected: true }\n : resolvedUnit;\n}\n\nexport function isNoUnit(unit?: UnitDefinitionLike): boolean {\n if (!unit) return true;\n return resolveUnit(unit.name).name === NO_UNIT;\n}\n","import Big from \"big.js\";\nimport type { QuantityWithUnitDef } from \"../types\";\nimport {\n getAverageValue,\n approximateFraction,\n DEFAULT_DENOMINATORS,\n DEFAULT_FRACTION_ACCURACY,\n DEFAULT_MAX_WHOLE,\n} from \"../quantities/numeric\";\nimport { UnitDefinition, SpecificUnitSystem, UnitType } from \"../types\";\nimport { isUnitCompatibleWithSystem } from \"./compatibility\";\nimport { units } from \"./definitions\";\n\nconst EPSILON = 0.01;\nconst DEFAULT_MAX_VALUE = 999;\n\n/**\n * Check if a value is \"close enough\" to an integer (within epsilon).\n */\nfunction isCloseToInteger(value: number): boolean {\n return Math.abs(value - Math.round(value)) < EPSILON;\n}\n\n/**\n * Get the maximum value threshold for a unit.\n * Beyond this value, we should upgrade to a larger unit.\n */\nfunction getMaxValue(unit: UnitDefinition): number {\n return unit.maxValue ?? DEFAULT_MAX_VALUE;\n}\n\n/**\n * Check if a value is in the valid range for a unit.\n * A value is valid if:\n * - It's \\>= 1 AND \\<= maxValue, OR\n * - It's \\< 1 AND can be approximated as a fraction (for units with fractions enabled)\n */\nfunction isValueInRange(value: number, unit: UnitDefinition): boolean {\n const maxValue = getMaxValue(unit);\n\n // Standard range: 1 to maxValue\n if (value >= 1 && value <= maxValue) {\n return true;\n }\n\n // Fraction range: values < 1 that can be approximated as fractions\n if (value > 0 && value < 1 && unit.fractions?.enabled) {\n const denominators = unit.fractions.denominators ?? DEFAULT_DENOMINATORS;\n const maxWhole = unit.fractions.maxWhole ?? DEFAULT_MAX_WHOLE;\n const fraction = approximateFraction(\n value,\n denominators,\n DEFAULT_FRACTION_ACCURACY,\n maxWhole,\n );\n return fraction !== null;\n }\n\n return false;\n}\n\n/**\n * Find the best unit for displaying a quantity.\n *\n * Algorithm:\n * 1. Get all candidate units of the same type that are compatible with the system\n * 2. Filter to candidates where value \\>= 1 and value \\<= maxValue (per-unit threshold)\n * 3. Only consider units with isBestUnit !== false\n * 4. Score: prefer integers in input family → integers in any family → smallest in range\n * 5. If none in range, pick the one closest to the range\n *\n * @param valueInBase - The value in base units (e.g., grams for mass, mL for volume)\n * @param unitType - The type of unit (mass, volume, count)\n * @param system - The system to use for conversion (metric, US, UK, JP)\n * @param inputUnits - The original input units (used as preferred \"family\")\n * @returns The best unit definition and the converted value\n */\nexport function findBestUnit(\n valueInBase: number,\n unitType: UnitType,\n system: SpecificUnitSystem,\n inputUnits: UnitDefinition[],\n): { unit: UnitDefinition; value: number } {\n const inputUnitNames = new Set(inputUnits.map((u) => u.name));\n // Get all candidate units of the same type compatible with the system, including input units\n const candidates = units.filter(\n (u) =>\n u.type === unitType &&\n isUnitCompatibleWithSystem(u, system) &&\n (u.isBestUnit !== false || inputUnitNames.has(u.name)),\n );\n\n /* v8 ignore start -- @preserve: defensive fallback that shouldn't happen with valid inputs */\n if (candidates.length === 0) {\n // Fallback: shouldn't happen, but return first input unit\n const fallbackUnit = inputUnits[0]!;\n return {\n unit: fallbackUnit,\n value: valueInBase / getToBase(fallbackUnit, system),\n };\n }\n /* v8 ignore stop */\n\n // Calculate value for each candidate\n const candidatesWithValues = candidates.map((unit) => ({\n unit,\n value: valueInBase / getToBase(unit, system),\n }));\n\n // Filter to valid range (including fraction-representable values), only for best-unit candidates\n const inRange = candidatesWithValues.filter((c) =>\n isValueInRange(c.value, c.unit),\n );\n\n if (inRange.length > 0) {\n // First priority: integers in input family\n const integersInInputFamily = inRange.filter(\n (c) => isCloseToInteger(c.value) && inputUnitNames.has(c.unit.name),\n );\n if (integersInInputFamily.length > 0) {\n // Return smallest integer in input family\n return integersInInputFamily.sort((a, b) => a.value - b.value)[0]!;\n }\n\n // Second priority: integers in any family (prefer system-appropriate units)\n const integersAny = inRange.filter((c) => isCloseToInteger(c.value));\n if (integersAny.length > 0) {\n // Sort by value\n return integersAny.sort((a, b) => a.value - b.value)[0]!;\n }\n\n // Third priority: smallest value in range (prioritizing input family)\n return inRange.sort((a, b) => {\n // Prioritize input family\n const aInFamily = inputUnitNames.has(a.unit.name) ? 0 : 1;\n const bInFamily = inputUnitNames.has(b.unit.name) ? 0 : 1;\n if (aInFamily !== bInFamily) return aInFamily - bInFamily;\n // Then by smallest value\n return a.value - b.value;\n })[0]!;\n }\n\n return candidatesWithValues.sort((a, b) => {\n const aMaxValue = getMaxValue(a.unit);\n const bMaxValue = getMaxValue(b.unit);\n const aDistance = a.value < 1 ? 1 - a.value : a.value - aMaxValue;\n const bDistance = b.value < 1 ? 1 - b.value : b.value - bMaxValue;\n return aDistance - bDistance;\n })[0]!;\n}\n\nexport function getUnitRatio(q1: QuantityWithUnitDef, q2: QuantityWithUnitDef) {\n const q1Value = getAverageValue(q1.quantity);\n const q2Value = getAverageValue(q2.quantity);\n const factor =\n \"toBase\" in q1.unit && \"toBase\" in q2.unit\n ? q1.unit.toBase / q2.unit.toBase\n : 1;\n\n if (typeof q1Value !== \"number\" || typeof q2Value !== \"number\") {\n throw Error(\n \"One of both values is not a number, so a ratio cannot be computed\",\n );\n }\n return Big(q1Value).times(factor).div(q2Value);\n}\n\nexport function getBaseUnitRatio(\n q: QuantityWithUnitDef,\n qRef: QuantityWithUnitDef,\n) {\n if (\"toBase\" in q.unit && \"toBase\" in qRef.unit) {\n return q.unit.toBase / qRef.unit.toBase;\n } else {\n return 1;\n }\n}\n\n/**\n * Get the toBase conversion factor for a unit, considering the system context.\n *\n * For ambiguous units:\n * - If a specific system is provided and the unit supports it, use that system's factor\n * - Otherwise, fall back to the unit's default toBase\n *\n * @param unit - The unit definition\n * @param system - Optional system context to use for ambiguous units\n * @returns The appropriate toBase conversion factor\n */\nexport function getToBase(\n unit: UnitDefinition,\n system?: SpecificUnitSystem,\n): number {\n if (unit.system === \"ambiguous\" && system && unit.toBaseBySystem) {\n return unit.toBaseBySystem[system] ?? unit.toBase;\n }\n return unit.toBase;\n}\n","import Big from \"big.js\";\nimport type {\n DecimalValue,\n FractionValue,\n FixedValue,\n Range,\n UnitDefinition,\n} from \"../types\";\n\n/** Default allowed denominators for fraction approximation */\nexport const DEFAULT_DENOMINATORS = [2, 3, 4];\n/** Default accuracy tolerance for fraction approximation (5%) */\nexport const DEFAULT_FRACTION_ACCURACY = 0.05;\n/** Default maximum whole number in mixed fraction before falling back to decimal */\nexport const DEFAULT_MAX_WHOLE = 4;\n\nfunction gcd(a: number, b: number): number {\n return b === 0 ? a : gcd(b, a % b);\n}\n\nexport function simplifyFraction(\n num: number,\n den: number,\n): DecimalValue | FractionValue {\n if (den === 0) {\n throw new Error(\"Denominator cannot be zero.\");\n }\n\n const commonDivisor = gcd(Math.abs(num), Math.abs(den));\n let simplifiedNum = num / commonDivisor;\n let simplifiedDen = den / commonDivisor;\n if (simplifiedDen < 0) {\n simplifiedNum = -simplifiedNum;\n simplifiedDen = -simplifiedDen;\n }\n\n if (simplifiedDen === 1) {\n return { type: \"decimal\", decimal: simplifiedNum };\n } else {\n return { type: \"fraction\", num: simplifiedNum, den: simplifiedDen };\n }\n}\n\n/**\n * Approximates a decimal value as a fraction within a given accuracy tolerance.\n * Returns an improper fraction (e.g., 1.25 → \\{ num: 5, den: 4 \\}) or null if no good match.\n *\n * @param value - The decimal value to approximate\n * @param denominators - Allowed denominators (default: [2, 3, 4, 8])\n * @param accuracy - Maximum relative error tolerance (default: 0.05 = 5%)\n * @param maxWhole - Maximum whole number part before returning null (default: 4)\n * @returns FractionValue if a good approximation exists, null otherwise\n */\nexport function approximateFraction(\n value: number,\n denominators: number[] = DEFAULT_DENOMINATORS,\n accuracy: number = DEFAULT_FRACTION_ACCURACY,\n maxWhole: number = DEFAULT_MAX_WHOLE,\n): FractionValue | null {\n // Only handle positive values\n if (value <= 0 || !Number.isFinite(value)) {\n return null;\n }\n\n // Check if whole part exceeds maxWhole\n const wholePart = Math.floor(value);\n if (wholePart > maxWhole) {\n return null;\n }\n\n // If value is very close to an integer, return null (use decimal instead)\n const fractionalPart = value - wholePart;\n if (fractionalPart < 1e-4) {\n return null;\n }\n\n let bestFraction: { num: number; den: number; error: number } | null = null;\n\n for (const den of denominators) {\n // Find the numerator that gives the closest approximation\n const exactNum = value * den;\n const roundedNum = Math.round(exactNum);\n\n // Skip if this would give 0 numerator\n if (roundedNum === 0) continue;\n\n const approximatedValue = roundedNum / den;\n const relativeError = Math.abs(approximatedValue - value) / value;\n\n // Check if within accuracy tolerance\n if (relativeError <= accuracy) {\n // Prefer smaller denominators (they come first in the array)\n // and smaller error for same denominator\n if (!bestFraction || relativeError < bestFraction.error) {\n bestFraction = { num: roundedNum, den, error: relativeError };\n }\n }\n }\n\n if (!bestFraction) {\n return null;\n }\n\n // Simplify the fraction\n const commonDivisor = gcd(bestFraction.num, bestFraction.den);\n return {\n type: \"fraction\",\n num: bestFraction.num / commonDivisor,\n den: bestFraction.den / commonDivisor,\n };\n}\n\nexport function getNumericValue(v: DecimalValue | FractionValue): number {\n if (v.type === \"decimal\") {\n return v.decimal;\n }\n return v.num / v.den;\n}\n\nexport function multiplyNumericValue(\n v: DecimalValue | FractionValue,\n factor: number | Big,\n): DecimalValue | FractionValue {\n if (v.type === \"decimal\") {\n return {\n type: \"decimal\",\n decimal: Big(v.decimal).times(factor).toNumber(),\n };\n }\n return simplifyFraction(Big(v.num).times(factor).toNumber(), v.den);\n}\n\nexport function addNumericValues(\n val1: DecimalValue | FractionValue,\n val2: DecimalValue | FractionValue,\n): DecimalValue | FractionValue {\n let num1: number;\n let den1: number;\n let num2: number;\n let den2: number;\n\n if (val1.type === \"decimal\") {\n num1 = val1.decimal;\n den1 = 1;\n } else {\n num1 = val1.num;\n den1 = val1.den;\n }\n\n if (val2.type === \"decimal\") {\n num2 = val2.decimal;\n den2 = 1;\n } else {\n num2 = val2.num;\n den2 = val2.den;\n }\n\n // Return 0 if both values are 0\n if (num1 === 0 && num2 === 0) {\n return { type: \"decimal\", decimal: 0 };\n }\n\n // We only return a fraction where both input values are fractions themselves or only one while the other is 0\n if (\n (val1.type === \"fraction\" && val2.type === \"fraction\") ||\n (val1.type === \"fraction\" &&\n val2.type === \"decimal\" &&\n val2.decimal === 0) ||\n (val2.type === \"fraction\" && val1.type === \"decimal\" && val1.decimal === 0)\n ) {\n const commonDen = den1 * den2;\n const sumNum = num1 * den2 + num2 * den1;\n return simplifyFraction(sumNum, commonDen);\n } else {\n return {\n type: \"decimal\",\n decimal: Big(num1).div(den1).add(Big(num2).div(den2)).toNumber(),\n };\n }\n}\n\n/**\n * Rounds a numeric value to the specified number of significant digits.\n * If the integer part has 4+ digits, preserves the full integer (rounds to nearest integer).\n * @param v - The value to round (decimal or fraction)\n * @param precision - Number of significant digits (default 3)\n * @returns A DecimalValue with the rounded result\n */\nexport const toRoundedDecimal = (\n v: DecimalValue | FractionValue,\n precision: number = 3,\n): DecimalValue => {\n const value = v.type === \"decimal\" ? v.decimal : v.num / v.den;\n\n // Handle zero specially\n if (value === 0) {\n return { type: \"decimal\", decimal: 0 };\n }\n\n const absValue = Math.abs(value);\n\n // If integer part has 4+ digits, round to nearest integer\n if (absValue >= 1000) {\n return { type: \"decimal\", decimal: Math.round(value) };\n }\n\n // Calculate the order of magnitude for significant digits\n const magnitude = Math.floor(Math.log10(absValue));\n const scale = Math.pow(10, precision - 1 - magnitude);\n const rounded = Math.round(value * scale) / scale;\n\n return { type: \"decimal\", decimal: rounded };\n};\n\n/**\n * Formats a numeric value for output, using fractions if the unit supports them\n * and the value can be well-approximated as a fraction, otherwise as a rounded decimal.\n *\n * @param value - The decimal value to format\n * @param unitDef - The unit definition (to check fraction config)\n * @param precision - Number of significant digits for decimal rounding (default 3)\n * @returns A DecimalValue or FractionValue\n */\nexport const formatOutputValue = (\n value: number,\n unitDef: UnitDefinition,\n precision: number = 3,\n): DecimalValue | FractionValue => {\n // Check if unit has fractions enabled\n if (unitDef.fractions?.enabled) {\n const denominators = unitDef.fractions.denominators ?? DEFAULT_DENOMINATORS;\n const maxWhole = unitDef.fractions.maxWhole ?? DEFAULT_MAX_WHOLE;\n\n const fraction = approximateFraction(\n value,\n denominators,\n DEFAULT_FRACTION_ACCURACY,\n maxWhole,\n );\n if (fraction) {\n return fraction;\n }\n }\n\n // Fall back to rounded decimal\n return toRoundedDecimal({ type: \"decimal\", decimal: value }, precision);\n};\n\nexport function multiplyQuantityValue(\n value: FixedValue | Range,\n factor: number | Big,\n): FixedValue | Range {\n if (value.type === \"fixed\") {\n const newValue = multiplyNumericValue(\n value.value as DecimalValue | FractionValue,\n Big(factor),\n );\n if (\n newValue.type === \"fraction\" &&\n (Big(factor).toNumber() === parseInt(Big(factor).toString()) || // e.g. 2 === int\n Big(1).div(factor).toNumber() ===\n parseInt(Big(1).div(factor).toString())) // e.g. 0.25 => 4 === int\n ) {\n // Preserve fractions\n return {\n type: \"fixed\",\n value: newValue,\n };\n }\n // We might multiply with big decimal number so rounding into decimal value\n return {\n type: \"fixed\",\n value: toRoundedDecimal(newValue),\n };\n }\n\n return {\n type: \"range\",\n min: multiplyNumericValue(value.min, factor),\n max: multiplyNumericValue(value.max, factor),\n };\n}\n\nexport function getAverageValue(q: FixedValue | Range): string | number {\n if (q.type === \"fixed\") {\n return q.value.type === \"text\" ? q.value.text : getNumericValue(q.value);\n } else {\n return (getNumericValue(q.min) + getNumericValue(q.max)) / 2;\n }\n}\n","import type {\n SpecificUnitSystem,\n UnitDefinition,\n UnitDefinitionLike,\n} from \"../types\";\n\n/**\n * Check if two unit-like objects are compatible for grouping.\n * Uses strict matching: same name OR (same type AND same system).\n * This is used for shopping list grouping where we don't want to\n * auto-merge different measurement systems.\n */\nexport function areUnitsGroupable(\n u1: UnitDefinitionLike,\n u2: UnitDefinitionLike,\n): boolean {\n if (u1.name === u2.name) {\n return true;\n }\n if (u1.type === \"other\" || u2.type === \"other\") {\n return false;\n }\n // Same type AND same system (or both ambiguous)\n if (u1.type === u2.type && u1.system === u2.system) {\n return true;\n }\n // Ambiguous units are compatible with units from systems they support\n // For grouping purposes, we treat ambiguous as compatible with metric (default) only if they have a metric definition\n if (u1.type === u2.type) {\n // Ambiguous units are compatible with metric ONLY if they have a metric definition\n if (\n u1.system === \"ambiguous\" &&\n u2.system === \"metric\" &&\n u1.toBaseBySystem?.metric !== undefined\n ) {\n return true;\n }\n if (\n u2.system === \"ambiguous\" &&\n u1.system === \"metric\" &&\n u2.toBaseBySystem?.metric !== undefined\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Check if two units are convertible for addition/conversion.\n * Units are convertible if:\n * - They have the same name, OR\n * - They have the same type (regardless of system - cross-system conversion is allowed)\n *\n * @param u1 - First unit definition\n * @param u2 - Second unit definition\n * @returns true if the units can be added/converted\n */\nexport function areUnitsConvertible(\n u1: UnitDefinition,\n u2: UnitDefinition,\n): boolean {\n if (u1.name === u2.name) return true;\n if (u1.type === \"other\" || u2.type === \"other\") return false;\n // Same type = compatible (cross-system conversion is allowed)\n return u1.type === u2.type;\n}\n\n/**\n * Check if a unit is compatible with a given system.\n * - Metric units are compatible with metric\n * - Ambiguous units are compatible if they have toBaseBySystem entry for the system\n * - Units of the specified system are always compatible\n */\nexport function isUnitCompatibleWithSystem(\n unit: UnitDefinition,\n system: SpecificUnitSystem,\n): boolean {\n if (unit.system === system) return true;\n if (unit.system === \"ambiguous\") {\n // Ambiguous units with toBaseBySystem are compatible only with systems they support\n /* v8 ignore else -- @preserve */\n if (unit.toBaseBySystem) {\n return system in unit.toBaseBySystem;\n }\n // Ambiguous units without specific system support are compatible with metric by default\n /* v8 ignore next -- @preserve: defensive fallback for ambiguous units without toBaseBySystem */\n if (system === \"metric\") return true;\n }\n /* v8 ignore else -- @preserve */\n if (unit.system === \"metric\" && system === \"JP\") {\n return true;\n }\n return false;\n}\n","import { IngredientFlag, CookwareFlag, NoProductMatchErrorCode } from \"./types\";\n\nexport class ReferencedItemCannotBeRedefinedError extends Error {\n constructor(\n item_type: \"ingredient\" | \"cookware\",\n item_name: string,\n new_modifier: IngredientFlag | CookwareFlag,\n ) {\n super(\n `The referenced ${item_type} \"${item_name}\" cannot be redefined as ${new_modifier}.\nYou can either remove the reference to create a new ${item_type} defined as ${new_modifier} or add the ${new_modifier} flag to the original definition of the ${item_type}`,\n );\n this.name = \"ReferencedItemCannotBeRedefinedError\";\n }\n}\n\n/**\n * Error thrown when trying to build a shopping cart without a product catalog\n * @category Errors\n */\nexport class NoProductCatalogForCartError extends Error {\n constructor() {\n super(\n `Cannot build a cart without a product catalog. Please set one using setProductCatalog()`,\n );\n this.name = \"NoProductCatalogForCartError\";\n }\n}\n\n/**\n * Error thrown when trying to build a shopping cart without a shopping list\n * @category Errors\n */\nexport class NoShoppingListForCartError extends Error {\n constructor() {\n super(\n `Cannot build a cart without a shopping list. Please set one using setShoppingList()`,\n );\n this.name = \"NoShoppingListForCartError\";\n }\n}\n\nexport class NoProductMatchError extends Error {\n code: NoProductMatchErrorCode;\n\n constructor(item_name: string, code: NoProductMatchErrorCode) {\n const messageMap: Record<NoProductMatchErrorCode, string> = {\n incompatibleUnits: `The units of the products in the catalogue are incompatible with ingredient ${item_name} in the shopping list.`,\n noProduct:\n \"No product was found linked to ingredient name ${item_name} in the shopping list\",\n textValue: `Ingredient ${item_name} has a text value as quantity and can therefore not be matched with any product in the catalogue.`,\n noQuantity: `Ingredient ${item_name} has no quantity and can therefore not be matched with any product in the catalogue.`,\n textValue_incompatibleUnits: `Multiple alternative quantities were provided for ingredient ${item_name} in the shopping list but they were either text values or no product in catalog were found to have compatible units`,\n };\n super(messageMap[code]);\n this.code = code;\n this.name = \"NoProductMatchError\";\n }\n}\n\nexport class InvalidProductCatalogFormat extends Error {\n constructor() {\n super(\"Invalid product catalog format.\");\n this.name = \"InvalidProductCatalogFormat\";\n }\n}\n\nexport class CannotAddTextValueError extends Error {\n constructor() {\n super(\"Cannot add a quantity with a text value.\");\n this.name = \"CannotAddTextValueError\";\n }\n}\n\nexport class IncompatibleUnitsError extends Error {\n constructor(unit1: string, unit2: string) {\n super(\n `Cannot add quantities with incompatible or unknown units: ${unit1} and ${unit2}`,\n );\n this.name = \"IncompatibleUnitsError\";\n }\n}\n\nexport class InvalidQuantityFormat extends Error {\n constructor(value: string, extra?: string) {\n super(\n `Invalid quantity format found in: ${value}${extra ? ` (${extra})` : \"\"}`,\n );\n this.name = \"InvalidQuantityFormat\";\n }\n}\n","import type {\n Group,\n OrGroup,\n AndGroup,\n QuantityWithUnitLike,\n DecimalValue,\n FractionValue,\n FixedValue,\n Range,\n IngredientQuantityGroup,\n IngredientQuantityAndGroup,\n AlternativeIngredientRef,\n} from \"../types\";\n\n// Helper type-checks (as before)\nexport function isGroup(x: QuantityWithUnitLike | Group): x is Group {\n return \"and\" in x || \"or\" in x;\n}\nexport function isOrGroup(x: QuantityWithUnitLike | Group): x is OrGroup {\n return isGroup(x) && \"or\" in x;\n}\n/**\n * Type guard to check if an ingredient quantity-like object is an AND group.\n * *\n * @param x - The quantity-like entry to check\n * @returns true if this is an AND group (has `and` property)\n * @category Helpers\n *\n * @example\n * ```typescript\n * for (const entry of ingredient.quantities) {\n * if (isAndGroup(entry)) {\n * // entry.and contains the list of quantities in the AND group\n * }\n * }\n * ```\n */\nexport function isAndGroup(\n x: IngredientQuantityGroup | IngredientQuantityAndGroup,\n): x is IngredientQuantityAndGroup;\nexport function isAndGroup(x: QuantityWithUnitLike | Group): x is AndGroup;\nexport function isAndGroup(\n x:\n | QuantityWithUnitLike\n | Group\n | IngredientQuantityGroup\n | IngredientQuantityAndGroup,\n): boolean {\n return \"and\" in x;\n}\nexport function isQuantity(\n x: QuantityWithUnitLike | Group,\n): x is QuantityWithUnitLike {\n return x && typeof x === \"object\" && \"quantity\" in x;\n}\n\n/**\n * Type guard to check if an ingredient quantity entry is a simple group.\n *\n * Simple groups have a single quantity with optional unit and equivalents.\n *\n * @param entry - The quantity entry to check\n * @returns true if this is a simple group (has `quantity` property)\n * @category Helpers\n *\n * @example\n * ```typescript\n * for (const entry of ingredient.quantities) {\n * if (isSimpleGroup(entry)) {\n * // entry.quantity is available\n * // entry.unit is available\n * }\n * }\n * ```\n */\nexport function isSimpleGroup(\n entry: IngredientQuantityGroup | IngredientQuantityAndGroup,\n): entry is IngredientQuantityGroup {\n return \"quantity\" in entry;\n}\n\nfunction isNumericValueIntegerLike(v: DecimalValue | FractionValue): boolean {\n if (v.type === \"decimal\") return Number.isInteger(v.decimal);\n // fraction: integer-like when numerator divisible by denominator\n return v.num % v.den === 0;\n}\n\nexport function isValueIntegerLike(q: FixedValue | Range): boolean {\n if (q.type === \"fixed\") {\n if (q.value.type === \"text\") return false;\n return isNumericValueIntegerLike(q.value);\n }\n // Range: integer-like when both min and max are integer-like\n return isNumericValueIntegerLike(q.min) && isNumericValueIntegerLike(q.max);\n}\n\n/**\n * Type guard to check if an ingredient quantity entry has alternatives.\n *\n * @param entry - The quantity entry to check\n * @returns true if this entry has alternatives\n * @category Helpers\n *\n * @example\n * ```typescript\n * for (const entry of ingredient.quantities) {\n * if (hasAlternatives(entry)) {\n * // entry.alternatives is available and non-empty\n * for (const alt of entry.alternatives) {\n * console.log(`Alternative ingredient index: ${alt.index}`);\n * }\n * }\n * }\n * ```\n */\nexport function hasAlternatives(\n entry: IngredientQuantityGroup | IngredientQuantityAndGroup,\n): entry is (IngredientQuantityGroup | IngredientQuantityAndGroup) & {\n alternatives: AlternativeIngredientRef[];\n} {\n return (\n \"alternatives\" in entry &&\n Array.isArray(entry.alternatives) &&\n entry.alternatives.length > 0\n );\n}\n","import type {\n FixedValue,\n Range,\n DecimalValue,\n FractionValue,\n Unit,\n UnitDefinition,\n QuantityWithPlainUnit,\n QuantityWithExtendedUnit,\n QuantityWithUnitDef,\n MaybeNestedGroup,\n MaybeNestedAndGroup,\n SpecificUnitSystem,\n} from \"../types\";\nimport { normalizeUnit, resolveUnit, isNoUnit } from \"../units/definitions\";\nimport { getToBase, findBestUnit } from \"../units/conversion\";\nimport { areUnitsConvertible } from \"../units/compatibility\";\nimport {\n addNumericValues,\n getNumericValue,\n formatOutputValue,\n getAverageValue,\n} from \"./numeric\";\nimport { CannotAddTextValueError, IncompatibleUnitsError } from \"../errors\";\nimport { isAndGroup, isOrGroup, isQuantity } from \"../utils/type_guards\";\n\n// `deNormalizeQuantity` is provided by `./math` and re-exported below.\n\nexport function extendAllUnits(\n q: QuantityWithPlainUnit | MaybeNestedGroup<QuantityWithPlainUnit>,\n): QuantityWithExtendedUnit | MaybeNestedGroup<QuantityWithExtendedUnit> {\n if (isAndGroup(q)) {\n return { and: q.and.map(extendAllUnits) };\n } else if (isOrGroup(q)) {\n return { or: q.or.map(extendAllUnits) };\n } else {\n const newQ: QuantityWithExtendedUnit = {\n quantity: q.quantity,\n };\n if (q.unit) {\n newQ.unit = { name: q.unit };\n }\n return newQ;\n }\n}\n\nexport function normalizeAllUnits(\n q: QuantityWithPlainUnit | MaybeNestedGroup<QuantityWithPlainUnit>,\n): QuantityWithUnitDef | MaybeNestedGroup<QuantityWithUnitDef> {\n if (isAndGroup(q)) {\n return { and: q.and.map(normalizeAllUnits) };\n } else if (isOrGroup(q)) {\n return { or: q.or.map(normalizeAllUnits) };\n } else {\n const newQ: QuantityWithUnitDef = {\n quantity: q.quantity,\n unit: resolveUnit(q.unit),\n };\n // If the quantity has equivalents, convert them to an OR group\n if (q.equivalents && q.equivalents.length > 0) {\n const equivalentsNormalized = q.equivalents.map((eq) =>\n normalizeAllUnits(eq),\n );\n return {\n or: [newQ, ...equivalentsNormalized] as QuantityWithUnitDef[],\n };\n }\n return newQ;\n }\n}\n\n/**\n * Get the default / neutral quantity which can be provided to addQuantity\n * for it to return the other value as result\n *\n * @return zero\n */\nexport function getDefaultQuantityValue(): FixedValue {\n return { type: \"fixed\", value: { type: \"decimal\", decimal: 0 } };\n}\n\n/**\n * Adds two quantity values together.\n *\n * - Adding two {@link FixedValue}s returns a new {@link FixedValue}.\n * - Adding a {@link Range} to any value returns a {@link Range}.\n *\n * @param v1 - The first quantity value.\n * @param v2 - The second quantity value.\n * @returns A new quantity value representing the sum.\n */\nexport function addQuantityValues(v1: FixedValue, v2: FixedValue): FixedValue;\nexport function addQuantityValues(\n v1: FixedValue | Range,\n v2: FixedValue | Range,\n): Range;\n\nexport function addQuantityValues(\n v1: FixedValue | Range,\n v2: FixedValue | Range,\n): FixedValue | Range {\n if (\n (v1.type === \"fixed\" && v1.value.type === \"text\") ||\n (v2.type === \"fixed\" && v2.value.type === \"text\")\n ) {\n throw new CannotAddTextValueError();\n }\n\n if (v1.type === \"fixed\" && v2.type === \"fixed\") {\n const res = addNumericValues(\n v1.value as DecimalValue | FractionValue,\n v2.value as DecimalValue | FractionValue,\n );\n return { type: \"fixed\", value: res };\n }\n const r1 =\n v1.type === \"range\" ? v1 : { type: \"range\", min: v1.value, max: v1.value };\n const r2 =\n v2.type === \"range\" ? v2 : { type: \"range\", min: v2.value, max: v2.value };\n const newMin = addNumericValues(\n r1.min as DecimalValue | FractionValue,\n r2.min as DecimalValue | FractionValue,\n );\n const newMax = addNumericValues(\n r1.max as DecimalValue | FractionValue,\n r2.max as DecimalValue | FractionValue,\n );\n return { type: \"range\", min: newMin, max: newMax };\n}\n\n/**\n * Adds two quantities, returning the result in the most appropriate unit.\n *\n * The \"best unit\" is selected based on:\n * 1. Filter candidates to units where `isBestUnit !== false`\n * 2. Use per-unit `maxValue` thresholds (prefer largest unit where value ≥ 1 and ≤ maxValue)\n * 3. Prefer integers in input unit family\n * 4. Prefer integers in any unit family\n * 5. If no integers, prefer smallest value in range\n *\n * @param q1 - The first quantity\n * @param q2 - The second quantity\n * @param system - Optional system context for resolving ambiguous units\n * @returns The sum of the two quantities\n */\nexport function addQuantities(\n q1: QuantityWithExtendedUnit,\n q2: QuantityWithExtendedUnit,\n system?: SpecificUnitSystem,\n): QuantityWithExtendedUnit {\n const v1 = q1.quantity;\n const v2 = q2.quantity;\n // Case 1: one of the two values is a text, we throw an error we can catch on the other end\n if (\n (v1.type === \"fixed\" && v1.value.type === \"text\") ||\n (v2.type === \"fixed\" && v2.value.type === \"text\")\n ) {\n throw new CannotAddTextValueError();\n }\n\n const unit1Def = normalizeUnit(q1.unit?.name);\n const unit2Def = normalizeUnit(q2.unit?.name);\n\n const addQuantityValuesAndSetUnit = (\n val1: FixedValue | Range,\n val2: FixedValue | Range,\n unit?: Unit,\n ): QuantityWithExtendedUnit => ({\n quantity: addQuantityValues(val1, val2),\n unit,\n });\n\n // Case 2: one of the two values doesn't have a unit, we preserve its value and consider its unit to be that of the other one\n // If at least one of the two units is \"\", this preserves it versus setting the resulting unit as undefined\n if (\n (q1.unit?.name === \"\" || q1.unit === undefined) &&\n q2.unit !== undefined\n ) {\n return addQuantityValuesAndSetUnit(v1, v2, q2.unit); // Prefer q2's unit\n }\n if (\n (q2.unit?.name === \"\" || q2.unit === undefined) &&\n q1.unit !== undefined\n ) {\n return addQuantityValuesAndSetUnit(v1, v2, q1.unit); // Prefer q1's unit\n }\n\n // Case 3: the two quantities have the exact same unit (or both no unit)\n if (!q1.unit && !q2.unit) {\n return addQuantityValuesAndSetUnit(v1, v2, q1.unit);\n }\n if (\n q1.unit &&\n q2.unit &&\n q1.unit.name.toLowerCase() === q2.unit.name.toLowerCase()\n ) {\n // Same unit - check if we should upgrade to a larger unit (e.g., 1200g → 1.2kg)\n if (unit1Def) {\n // Known unit type - use findBestUnit to potentially upgrade\n const effectiveSystem =\n system ??\n ([\"metric\", \"JP\"].includes(unit1Def.system)\n ? (unit1Def.system as \"metric\" | \"JP\")\n : \"US\");\n return addAndFindBestUnit(v1, v2, unit1Def, unit1Def, effectiveSystem, [\n unit1Def,\n ]);\n }\n // Unknown unit type - just add values, keep unit\n return addQuantityValuesAndSetUnit(v1, v2, q1.unit);\n }\n\n // Case 4: the two quantities have different units of known type\n if (unit1Def && unit2Def) {\n // Throw error if units aren't convertible (not of same type)\n if (!areUnitsConvertible(unit1Def, unit2Def)) {\n throw new IncompatibleUnitsError(\n `${unit1Def.type} (${q1.unit?.name})`,\n `${unit2Def.type} (${q2.unit?.name})`,\n );\n }\n\n // Determine the effective system for conversion of ambiguous units\n let effectiveSystem = system;\n\n // If no system provided, infer based on the input units:\n // 1. Prefer metric if either unit is metric\n // 2. If both are ambiguous and US-compatible, use US\n // 3. Default to metric\n // v8 ignore else -- @preserve\n if (!effectiveSystem) {\n if (unit1Def.system === \"metric\" || unit2Def.system === \"metric\") {\n effectiveSystem = \"metric\";\n } else {\n // TODO remove if v8 marker if JP is augmented with more than one unit */\n // v8 ignore if -- @preserve\n if (unit1Def.system === \"JP\" && unit2Def.system === \"JP\") {\n effectiveSystem = \"JP\";\n } else {\n // Check if both units are US-compatible\n const unit1SupportsUS =\n unit1Def.system === \"US\" ||\n (unit1Def.system === \"ambiguous\" &&\n unit1Def.toBaseBySystem &&\n \"US\" in unit1Def.toBaseBySystem);\n const unit2SupportsUS =\n unit2Def.system === \"US\" ||\n (unit2Def.system === \"ambiguous\" &&\n unit2Def.toBaseBySystem &&\n \"US\" in unit2Def.toBaseBySystem);\n effectiveSystem =\n unit1SupportsUS && unit2SupportsUS ? \"US\" : \"metric\";\n }\n }\n }\n\n return addAndFindBestUnit(v1, v2, unit1Def, unit2Def, effectiveSystem, [\n unit1Def,\n unit2Def,\n ]);\n }\n\n // Case 5: the two quantities have different units of unknown type\n throw new IncompatibleUnitsError(\n q1.unit?.name as string,\n q2.unit?.name as string,\n );\n}\n\n/**\n * Helper function to add two quantities and find the best unit for the result.\n */\nfunction addAndFindBestUnit(\n v1: FixedValue | Range,\n v2: FixedValue | Range,\n unit1Def: UnitDefinition,\n unit2Def: UnitDefinition,\n system: SpecificUnitSystem,\n inputUnits: UnitDefinition[],\n): QuantityWithExtendedUnit {\n // Convert both values to base units and sum\n const toBase1 = getToBase(unit1Def, system);\n const toBase2 = getToBase(unit2Def, system);\n\n // Get the sum in base units\n let sumInBase: number;\n if (v1.type === \"fixed\" && v2.type === \"fixed\") {\n const val1 = getNumericValue(v1.value as DecimalValue | FractionValue);\n const val2 = getNumericValue(v2.value as DecimalValue | FractionValue);\n sumInBase = val1 * toBase1 + val2 * toBase2;\n } else {\n // Handle ranges by using average for best unit selection\n const avg1 = getAverageValue(v1) as number;\n const avg2 = getAverageValue(v2) as number;\n sumInBase = avg1 * toBase1 + avg2 * toBase2;\n }\n\n // Find the best unit\n const { unit: bestUnit, value: bestValue } = findBestUnit(\n sumInBase,\n unit1Def.type,\n system,\n inputUnits,\n );\n\n // Format the value (uses fractions if unit supports them)\n const formattedValue = formatOutputValue(bestValue, bestUnit);\n\n // Handle ranges: scale the range to the best unit\n if (v1.type === \"range\" || v2.type === \"range\") {\n const r1 =\n v1.type === \"range\"\n ? v1\n : { type: \"range\" as const, min: v1.value, max: v1.value };\n const r2 =\n v2.type === \"range\"\n ? v2\n : { type: \"range\" as const, min: v2.value, max: v2.value };\n\n const minInBase =\n getNumericValue(r1.min as DecimalValue | FractionValue) * toBase1 +\n getNumericValue(r2.min as DecimalValue | FractionValue) * toBase2;\n const maxInBase =\n getNumericValue(r1.max as DecimalValue | FractionValue) * toBase1 +\n getNumericValue(r2.max as DecimalValue | FractionValue) * toBase2;\n\n const bestToBase = getToBase(bestUnit, system);\n const minValue = minInBase / bestToBase;\n const maxValue = maxInBase / bestToBase;\n\n return {\n quantity: {\n type: \"range\",\n min: formatOutputValue(minValue, bestUnit),\n max: formatOutputValue(maxValue, bestUnit),\n },\n unit: { name: bestUnit.name },\n };\n }\n\n return {\n quantity: { type: \"fixed\", value: formattedValue },\n unit: { name: bestUnit.name },\n };\n}\n\n/**\n * Converts a quantity to the best unit in a target system.\n * Returns the converted quantity, or undefined if the unit type is \"other\" or not convertible.\n *\n * @category Helpers\n *\n * @param quantity - The quantity to convert\n * @param system - The target unit system\n * @returns The converted quantity, or undefined if conversion not possible\n */\n\nexport function convertQuantityToSystem(\n quantity: QuantityWithPlainUnit,\n system: SpecificUnitSystem,\n): QuantityWithPlainUnit | undefined;\nexport function convertQuantityToSystem(\n quantity: QuantityWithExtendedUnit,\n system: SpecificUnitSystem,\n): QuantityWithExtendedUnit | undefined;\nexport function convertQuantityToSystem(\n quantity: QuantityWithPlainUnit | QuantityWithExtendedUnit,\n system: SpecificUnitSystem,\n): QuantityWithPlainUnit | QuantityWithExtendedUnit | undefined {\n const unitDef = resolveUnit(\n typeof quantity.unit === \"string\" ? quantity.unit : quantity.unit?.name,\n );\n\n // Cannot convert \"other\" type units or units without toBase\n if (unitDef.type === \"other\" || !(\"toBase\" in unitDef)) {\n return undefined;\n }\n\n const avgValue = getAverageValue(quantity.quantity);\n if (typeof avgValue !== \"number\") {\n return undefined;\n }\n\n const toBase = getToBase(unitDef, system);\n const valueInBase = avgValue * toBase;\n const { unit: bestUnit, value: bestValue } = findBestUnit(\n valueInBase,\n unitDef.type,\n system,\n [unitDef],\n );\n\n // Format the value (uses fractions if unit supports them)\n const formattedValue = formatOutputValue(bestValue, bestUnit);\n\n // Handle ranges\n if (quantity.quantity.type === \"range\") {\n const bestToBase = getToBase(bestUnit, system);\n\n const minValue =\n (getNumericValue(quantity.quantity.min) * toBase) / bestToBase;\n const maxValue =\n (getNumericValue(quantity.quantity.max) * toBase) / bestToBase;\n\n return {\n quantity: {\n type: \"range\",\n min: formatOutputValue(minValue, bestUnit),\n max: formatOutputValue(maxValue, bestUnit),\n },\n unit: { name: bestUnit.name },\n };\n }\n\n return {\n quantity: { type: \"fixed\", value: formattedValue },\n unit: { name: bestUnit.name },\n };\n}\n\nexport function toPlainUnit(\n quantity:\n | QuantityWithExtendedUnit\n | MaybeNestedGroup<QuantityWithExtendedUnit>,\n): QuantityWithPlainUnit | MaybeNestedGroup<QuantityWithPlainUnit> {\n if (isQuantity(quantity))\n return quantity.unit\n ? { ...quantity, unit: quantity.unit.name }\n : (quantity as QuantityWithPlainUnit);\n else if (isOrGroup(quantity)) {\n return {\n or: quantity.or.map(toPlainUnit),\n } as MaybeNestedGroup<QuantityWithPlainUnit>;\n } else {\n return {\n and: quantity.and.map(toPlainUnit),\n } as MaybeNestedGroup<QuantityWithPlainUnit>;\n }\n}\n\n// Convert plain unit to extended unit format for addEquivalentsAndSimplify\n// Overloads for precise return types\nexport function toExtendedUnit(\n q: QuantityWithPlainUnit,\n): QuantityWithExtendedUnit;\nexport function toExtendedUnit(\n q: MaybeNestedGroup<QuantityWithPlainUnit>,\n): MaybeNestedGroup<QuantityWithExtendedUnit>;\nexport function toExtendedUnit(\n q: QuantityWithPlainUnit | MaybeNestedGroup<QuantityWithPlainUnit>,\n): QuantityWithExtendedUnit | MaybeNestedGroup<QuantityWithExtendedUnit>;\nexport function toExtendedUnit(\n q: QuantityWithPlainUnit | MaybeNestedGroup<QuantityWithPlainUnit>,\n): QuantityWithExtendedUnit | MaybeNestedGroup<QuantityWithExtendedUnit> {\n if (isQuantity(q)) {\n return q.unit\n ? { ...q, unit: { name: q.unit } }\n : (q as QuantityWithExtendedUnit);\n } else if (isOrGroup(q)) {\n return { or: q.or.map(toExtendedUnit) };\n } else {\n return { and: q.and.map(toExtendedUnit) };\n }\n}\n\nexport function deNormalizeQuantity(\n q: QuantityWithUnitDef,\n): QuantityWithExtendedUnit {\n const result: QuantityWithExtendedUnit = {\n quantity: q.quantity,\n };\n if (!isNoUnit(q.unit)) {\n result.unit = { name: q.unit.name };\n }\n return result;\n}\n\n// Helper function to convert addEquivalentsAndSimplify result to Ingredient.quantities format\n// Returns either a QuantityWithPlainUnit (for simple/OR groups) or an IngredientQuantityAndGroup (for AND groups)\nexport const flattenPlainUnitGroup = (\n summed: QuantityWithPlainUnit | MaybeNestedGroup<QuantityWithPlainUnit>,\n): (\n | QuantityWithPlainUnit\n | {\n and: QuantityWithPlainUnit[];\n equivalents?: QuantityWithPlainUnit[];\n }\n)[] => {\n if (isOrGroup(summed)) {\n // OR group: check if first entry is an AND group (nested OR-with-AND case from addEquivalentsAndSimplify)\n // This happens when we have incompatible integer-protected primaries with compatible equivalents\n // e.g., { or: [{ and: [large, small] }, cup] }\n const entries = summed.or;\n const andGroupEntry = entries.find(\n (e): e is MaybeNestedGroup<QuantityWithPlainUnit> => isAndGroup(e),\n );\n\n if (andGroupEntry) {\n // Nested OR-with-AND case: AND group of primaries + equivalents\n const andEntries: QuantityWithPlainUnit[] = [];\n // Double casting due to:\n // - Nested ORs are flattened already\n // - Double nesting is not possible in this context\n const addGroupEntryContent = (\n andGroupEntry as MaybeNestedAndGroup<QuantityWithPlainUnit>\n ).and as QuantityWithPlainUnit[];\n for (const entry of addGroupEntryContent) {\n andEntries.push({\n quantity: entry.quantity,\n ...(entry.unit && { unit: entry.unit }),\n });\n }\n\n // The other entries in the OR group are the equivalents\n const equivalentsList: QuantityWithPlainUnit[] = entries\n .filter((e): e is QuantityWithPlainUnit => isQuantity(e))\n .map((e) => ({ quantity: e.quantity, unit: e.unit }));\n\n if (equivalentsList.length > 0) {\n return [\n {\n and: andEntries,\n equivalents: equivalentsList,\n },\n ];\n } else {\n // No equivalents: flatten to separate entries (shouldn't happen in this branch, but handle it)\n return andEntries;\n }\n }\n\n // Simple OR group: first entry is primary, rest are equivalents\n const simpleEntries = entries.filter((e): e is QuantityWithPlainUnit =>\n isQuantity(e),\n );\n /* v8 ignore else -- @preserve */\n if (simpleEntries.length > 0) {\n const result: QuantityWithPlainUnit = {\n quantity: simpleEntries[0]!.quantity,\n unit: simpleEntries[0]!.unit,\n };\n if (simpleEntries.length > 1) {\n result.equivalents = simpleEntries.slice(1);\n }\n return [result];\n }\n // Fallback: use first entry regardless\n else {\n const first = entries[0] as QuantityWithPlainUnit;\n return [{ quantity: first.quantity, unit: first.unit }];\n }\n } else if (isAndGroup(summed)) {\n // AND group: check if entries have OR groups (equivalents that can be extracted)\n const andEntries: QuantityWithPlainUnit[] = [];\n const equivalentsList: QuantityWithPlainUnit[] = [];\n for (const entry of summed.and) {\n // Double-nesting is not possible in this context\n // v8 ignore else -- @preserve\n if (isOrGroup(entry)) {\n // This entry has equivalents: first is primary, rest are equivalents\n const orEntries = entry.or as QuantityWithPlainUnit[];\n andEntries.push({\n quantity: orEntries[0]!.quantity,\n ...(orEntries[0]!.unit && { unit: orEntries[0]!.unit }),\n });\n // Collect equivalents for later merging\n equivalentsList.push(...orEntries.slice(1));\n } else if (isQuantity(entry)) {\n // Simple quantity, no equivalents\n andEntries.push({\n quantity: entry.quantity,\n ...(entry.unit && { unit: entry.unit }),\n });\n }\n }\n\n // Build the AND group result\n // If there are no equivalents, flatten to separate groupQuantity entries (water case)\n // If there are equivalents, return an AND group with the summed equivalents (carrots case)\n if (equivalentsList.length === 0) {\n // No equivalents: flatten to separate entries\n return andEntries;\n }\n\n const result: {\n and: QuantityWithPlainUnit[];\n equivalents?: QuantityWithPlainUnit[];\n } = {\n and: andEntries,\n equivalents: equivalentsList,\n };\n\n return [result];\n } else {\n // Simple QuantityWithPlainUnit\n return [\n { quantity: summed.quantity, ...(summed.unit && { unit: summed.unit }) },\n ];\n }\n};\n\n/**\n * Apply the best unit to a quantity based on its value and unit system.\n * Converts the quantity to base units, finds the best unit for display,\n * and returns a new quantity with the best unit.\n *\n * @param q - The quantity to optimize\n * @param system - The unit system to use for finding the best unit. If not provided,\n * the system is inferred from the unit (metric/JP stay as-is, others default to US).\n * @returns A new quantity with the best unit, or the original if no conversion possible\n */\nexport function applyBestUnit(\n q: QuantityWithExtendedUnit,\n system?: SpecificUnitSystem,\n): QuantityWithExtendedUnit {\n // Skip if no unit or text value\n if (!q.unit?.name) {\n return q;\n }\n\n const unitDef = resolveUnit(q.unit.name);\n\n // Skip if unit type is \"other\" (not convertible)\n if (unitDef.type === \"other\") {\n return q;\n }\n\n // Get the value - skip if text\n if (q.quantity.type === \"fixed\" && q.quantity.value.type === \"text\") {\n return q;\n }\n\n // string is filtered out in the above if\n const avgValue = getAverageValue(q.quantity) as number;\n\n // Determine effective system: use provided system, or infer from unit\n const effectiveSystem: SpecificUnitSystem =\n system ??\n ([\"metric\", \"JP\"].includes(unitDef.system)\n ? (unitDef.system as \"metric\" | \"JP\")\n : \"US\");\n\n // Convert to base units\n const toBase = getToBase(unitDef, effectiveSystem);\n const valueInBase = avgValue * toBase;\n\n // Find the best unit\n const { unit: bestUnit, value: bestValue } = findBestUnit(\n valueInBase,\n unitDef.type,\n effectiveSystem,\n [unitDef],\n );\n\n // Get canonical name of the original unit for comparison\n const originalCanonicalName = normalizeUnit(q.unit.name)?.name;\n\n // If same unit (by canonical name match), no change needed - preserve original unit name\n if (bestUnit.name === originalCanonicalName) {\n return q;\n }\n\n // Format the value for the best unit\n const formattedValue = formatOutputValue(bestValue, bestUnit);\n\n // Handle ranges: scale to the best unit\n if (q.quantity.type === \"range\") {\n const bestToBase = getToBase(bestUnit, effectiveSystem);\n const minValue = (getNumericValue(q.quantity.min) * toBase) / bestToBase;\n const maxValue = (getNumericValue(q.quantity.max) * toBase) / bestToBase;\n\n return {\n quantity: {\n type: \"range\",\n min: formatOutputValue(minValue, bestUnit),\n max: formatOutputValue(maxValue, bestUnit),\n },\n unit: { name: bestUnit.name },\n };\n }\n\n // Fixed value\n return {\n quantity: {\n type: \"fixed\",\n value: formattedValue,\n },\n unit: { name: bestUnit.name },\n };\n}\n","import type {\n MetadataExtract,\n Metadata,\n FixedValue,\n Range,\n TextValue,\n DecimalValue,\n FractionValue,\n NoteItem,\n SpecificUnitSystem,\n} from \"../types\";\nimport {\n metadataRegex,\n rangeRegex,\n numberLikeRegex,\n scalingMetaValueRegex,\n} from \"../regex\";\nimport { Section as SectionObject } from \"../classes/section\";\nimport type { Ingredient, Step, Cookware } from \"../types\";\nimport { addQuantityValues } from \"../quantities/mutations\";\nimport {\n CannotAddTextValueError,\n ReferencedItemCannotBeRedefinedError,\n} from \"../errors\";\n\n/**\n * Pushes a pending note to the section content if it has items.\n * @param section - The current section object.\n * @param noteItems - The note items array.\n * @returns An empty array if the note was pushed, otherwise the original items.\n */\nexport function flushPendingNote(\n section: SectionObject,\n noteItems: NoteItem[],\n): NoteItem[] {\n if (noteItems.length > 0) {\n section.content.push({ type: \"note\", items: [...noteItems] });\n return [];\n }\n return noteItems;\n}\n\n/**\n * Pushes pending step items and a pending note to the section content.\n * @param section - The current section object.\n * @param items - The list of step items. This array will be cleared.\n * @returns true if the items were pushed, otherwise false.\n */\nexport function flushPendingItems(\n section: SectionObject,\n items: Step[\"items\"],\n): boolean {\n if (items.length > 0) {\n section.content.push({ type: \"step\", items: [...items] });\n items.length = 0;\n return true;\n }\n return false;\n}\n\n/**\n * Finds an ingredient in the list (case-insensitively) and updates it, or adds it if not present.\n * This function mutates the `ingredients` array.\n * @param ingredients - The list of ingredients.\n * @param newIngredient - The ingredient to find or add.\n * @param isReference - Whether this is a reference ingredient (`&` modifier).\n * @returns The index of the ingredient in the list.\n * @returns An object containing the index of the ingredient and its quantity part in the list.\n */\nexport function findAndUpsertIngredient(\n ingredients: Ingredient[],\n newIngredient: Ingredient,\n isReference: boolean,\n): number {\n const { name } = newIngredient;\n\n if (isReference) {\n const indexFind = ingredients.findIndex(\n (i) => i.name.toLowerCase() === name.toLowerCase(),\n );\n\n if (indexFind === -1) {\n throw new Error(\n `Referenced ingredient \"${name}\" not found. A referenced ingredient must be declared before being referenced with '&'.`,\n );\n }\n\n // Ingredient already exists\n const existingIngredient = ingredients[indexFind]!;\n\n // Checking whether any provided flags are the same as the original ingredient\n // TODO: backport fix (check on array length) to v2\n if (!newIngredient.flags) {\n if (\n Array.isArray(existingIngredient.flags) &&\n existingIngredient.flags.length > 0\n ) {\n throw new ReferencedItemCannotBeRedefinedError(\n \"ingredient\",\n existingIngredient.name,\n existingIngredient.flags[0]!,\n );\n }\n } else {\n for (const flag of newIngredient.flags) {\n /* v8 ignore else -- @preserve */\n if (\n existingIngredient.flags === undefined ||\n !existingIngredient.flags.includes(flag)\n ) {\n throw new ReferencedItemCannotBeRedefinedError(\n \"ingredient\",\n existingIngredient.name,\n flag,\n );\n }\n }\n }\n\n return indexFind;\n }\n\n // Not a reference, so add as a new ingredient.\n return ingredients.push(newIngredient) - 1;\n}\n\nexport function findAndUpsertCookware(\n cookware: Cookware[],\n newCookware: Cookware,\n isReference: boolean,\n): number {\n const { name, quantity } = newCookware;\n\n if (isReference) {\n const index = cookware.findIndex(\n (i) => i.name.toLowerCase() === name.toLowerCase(),\n );\n\n if (index === -1) {\n throw new Error(\n `Referenced cookware \"${name}\" not found. A referenced cookware must be declared before being referenced with '&'.`,\n );\n }\n\n const existingCookware = cookware[index]!;\n\n // Checking whether any provided flags are the same as the original cookware\n // TODO: backport fix (if/else) + check on array length to v2\n if (!newCookware.flags) {\n if (\n Array.isArray(existingCookware.flags) &&\n existingCookware.flags.length > 0\n ) {\n throw new ReferencedItemCannotBeRedefinedError(\n \"cookware\",\n existingCookware.name,\n existingCookware.flags[0]!,\n );\n }\n } else {\n for (const flag of newCookware.flags) {\n /* v8 ignore else -- @preserve */\n if (\n existingCookware.flags === undefined ||\n !existingCookware.flags.includes(flag)\n ) {\n throw new ReferencedItemCannotBeRedefinedError(\n \"cookware\",\n existingCookware.name,\n flag,\n );\n }\n }\n }\n\n if (quantity !== undefined) {\n if (!existingCookware.quantity) {\n existingCookware.quantity = quantity;\n } else {\n try {\n existingCookware.quantity = addQuantityValues(\n existingCookware.quantity,\n quantity,\n );\n } catch (e) {\n /* v8 ignore else -- expliciting error type -- @preserve */\n if (e instanceof CannotAddTextValueError) {\n return cookware.push(newCookware) - 1;\n }\n }\n }\n }\n return index;\n }\n\n return cookware.push(newCookware) - 1;\n}\n\n// Parser when we know the input is either a number-like value\nexport const parseFixedValue = (\n input_str: string,\n): TextValue | DecimalValue | FractionValue => {\n if (!numberLikeRegex.test(input_str)) {\n return { type: \"text\", text: input_str };\n }\n\n // After this we know that s is either a fraction or a decimal value\n const s = input_str.trim().replace(\",\", \".\");\n\n // fraction\n if (s.includes(\"/\")) {\n const parts = s.split(\"/\");\n\n const num = Number(parts[0]);\n const den = Number(parts[1]);\n\n return { type: \"fraction\", num, den };\n }\n\n // decimal\n return { type: \"decimal\", decimal: Number(s) };\n};\n\nexport function stringifyQuantityValue(quantity: FixedValue | Range): string {\n if (quantity.type === \"fixed\") {\n return stringifyFixedValue(quantity);\n } else {\n return `${stringifyFixedValue({ type: \"fixed\", value: quantity.min })}-${stringifyFixedValue({ type: \"fixed\", value: quantity.max })}`;\n }\n}\n\nfunction stringifyFixedValue(quantity: FixedValue): string {\n if (quantity.value.type === \"fraction\")\n return `${quantity.value.num}/${quantity.value.den}`;\n else if (quantity.value.type === \"decimal\")\n return String(quantity.value.decimal);\n else return quantity.value.text;\n}\n\n// TODO: rename to parseQuantityValue\nexport function parseQuantityInput(input_str: string): FixedValue | Range {\n const clean_str = String(input_str).trim();\n\n if (rangeRegex.test(clean_str)) {\n const range_parts = clean_str.split(\"-\");\n // As we've tested for it, we know that we have Number-like Quantities to parse\n const min = parseFixedValue(range_parts[0]!.trim()) as\n | DecimalValue\n | FractionValue;\n const max = parseFixedValue(range_parts[1]!.trim()) as\n | DecimalValue\n | FractionValue;\n return { type: \"range\", min, max };\n }\n\n return { type: \"fixed\", value: parseFixedValue(clean_str) };\n}\n\nexport function parseSimpleMetaVar(content: string, varName: string) {\n const varMatch = content.match(\n new RegExp(`^${varName}:\\\\s*(.*(?:\\\\r?\\\\n\\\\s+.*)*)+`, \"m\"),\n );\n return varMatch\n ? varMatch[1]?.trim().replace(/\\s*\\r?\\n\\s+/g, \" \")\n : undefined;\n}\n\nexport function parseScalingMetaVar(\n content: string,\n varName: string,\n): [number, string] | undefined {\n const varMatch = content.match(scalingMetaValueRegex(varName));\n if (!varMatch) return undefined;\n if (isNaN(Number(varMatch[2]?.trim()))) {\n throw new Error(\"Scaling variables should be numbers\");\n }\n return [Number(varMatch[2]?.trim()), varMatch[1]!.trim()];\n}\n\nexport function parseListMetaVar(content: string, varName: string) {\n // Handle both inline and YAML-style tags\n const listMatch = content.match(\n new RegExp(\n `^${varName}:\\\\s*(?:\\\\[([^\\\\]]*)\\\\]|((?:\\\\r?\\\\n\\\\s*-\\\\s*.+)+))`,\n \"m\",\n ),\n );\n if (!listMatch) return undefined;\n\n /* v8 ignore else -- @preserve */\n if (listMatch[1] !== undefined) {\n // Inline list: tags: [one, two, three]\n return listMatch[1].split(\",\").map((tag) => tag.trim());\n } else if (listMatch[2]) {\n // YAML list:\n // tags:\n // - one\n // - two\n return listMatch[2]\n .split(\"\\n\")\n .filter((line) => line.trim() !== \"\")\n .map((line) => line.replace(/^\\s*-\\s*/, \"\").trim());\n }\n}\n\nexport function extractMetadata(content: string): MetadataExtract {\n const metadata: Metadata = {};\n let servings: number | undefined = undefined;\n\n // Is there front-matter at all?\n const metadataContent = content.match(metadataRegex)?.[2];\n if (!metadataContent) {\n return { metadata };\n }\n\n // String metadata variables\n for (const metaVar of [\n \"title\",\n \"source\",\n \"source.name\",\n \"source.url\",\n \"author\",\n \"source.author\",\n \"prep time\",\n \"time.prep\",\n \"cook time\",\n \"time.cook\",\n \"time required\",\n \"time\",\n \"duration\",\n \"locale\",\n \"introduction\",\n \"description\",\n \"course\",\n \"category\",\n \"diet\",\n \"cuisine\",\n \"difficulty\",\n \"image\",\n \"picture\",\n ] as (keyof Pick<\n Metadata,\n | \"title\"\n | \"source\"\n | \"source.name\"\n | \"source.url\"\n | \"author\"\n | \"source.author\"\n | \"prep time\"\n | \"time.prep\"\n | \"cook time\"\n | \"time.cook\"\n | \"time required\"\n | \"time\"\n | \"duration\"\n | \"locale\"\n | \"introduction\"\n | \"description\"\n | \"course\"\n | \"category\"\n | \"diet\"\n | \"cuisine\"\n | \"difficulty\"\n | \"image\"\n | \"picture\"\n >)[]) {\n const stringMetaValue = parseSimpleMetaVar(metadataContent, metaVar);\n if (stringMetaValue) metadata[metaVar] = stringMetaValue;\n }\n\n // Unit system (case-insensitive normalization)\n let unitSystem: SpecificUnitSystem | undefined;\n const unitSystemRaw = parseSimpleMetaVar(metadataContent, \"unit system\");\n if (unitSystemRaw) {\n metadata[\"unit system\"] = unitSystemRaw;\n const unitSystemMap: Record<string, SpecificUnitSystem> = {\n metric: \"metric\",\n us: \"US\",\n uk: \"UK\",\n jp: \"JP\",\n };\n unitSystem = unitSystemMap[unitSystemRaw.toLowerCase()];\n }\n\n // String metadata variables\n for (const metaVar of [\"serves\", \"yield\", \"servings\"] as (keyof Pick<\n Metadata,\n \"servings\" | \"yield\" | \"serves\"\n >)[]) {\n const scalingMetaValue = parseScalingMetaVar(metadataContent, metaVar);\n if (scalingMetaValue && scalingMetaValue[1]) {\n metadata[metaVar] = scalingMetaValue[1];\n servings = scalingMetaValue[0];\n }\n }\n\n // List metadata variables\n for (const metaVar of [\"tags\", \"images\", \"pictures\"] as (keyof Pick<\n Metadata,\n \"tags\" | \"images\" | \"pictures\"\n >)[]) {\n const listMetaValue = parseListMetaVar(metadataContent, metaVar);\n if (listMetaValue) metadata[metaVar] = listMetaValue;\n }\n\n return { metadata, servings, unitSystem };\n}\n\nexport function isPositiveIntegerString(str: string): boolean {\n return /^\\d+$/.test(str);\n}\n\nexport function unionOfSets<T>(s1: Set<T>, s2: Set<T>): Set<T> {\n const result = new Set(s1);\n for (const item of s2) {\n result.add(item);\n }\n return result;\n}\n\n/**\n * Returns a canonical string key from sorted alternative indices for grouping quantities.\n * Used to determine if two ingredient items have the same alternatives and can be summed together.\n * @param alternatives - Array of alternative ingredient references\n * @returns A string of sorted indices (e.g., \"0,2,5\") or null if no alternatives\n */\nexport function getAlternativeSignature(\n alternatives: { index: number }[] | undefined,\n): string | null {\n if (!alternatives || alternatives.length === 0) return null;\n return alternatives\n .map((a) => a.index)\n .sort((a, b) => a - b)\n .join(\",\");\n}\n","import type { Step, Note } from \"../types\";\n\n/**\n * Represents a recipe section\n *\n * Wrapped as a _Class_ and not defined as a simple _Type_ to expose some useful helper\n * classes (e.g. {@link Section.isBlank | isBlank()})\n *\n * @category Types\n */\nexport class Section {\n /**\n * The name of the section. Can be an empty string for the default (first) section.\n * @defaultValue `\"\"`\n */\n name: string;\n /** An array of steps and notes that make up the content of the section. */\n content: (Step | Note)[] = [];\n\n /**\n * Creates an instance of Section.\n * @param name - The name of the section. Defaults to an empty string.\n */\n constructor(name: string = \"\") {\n this.name = name;\n }\n\n /**\n * Checks if the section is blank (has no name and no content).\n * Used during recipe parsing\n * @returns `true` if the section is blank, otherwise `false`.\n */\n isBlank(): boolean {\n return this.name === \"\" && this.content.length === 0;\n }\n}\n","import { isNoUnit } from \"../units/definitions\";\nimport type {\n QuantityWithPlainUnit,\n QuantityWithExtendedUnit,\n QuantityWithUnitDef,\n UnitDefinitionLike,\n FlatOrGroup,\n MaybeNestedOrGroup,\n FlatAndGroup,\n MaybeNestedGroup,\n SpecificUnitSystem,\n} from \"../types\";\nimport { resolveUnit } from \"../units/definitions\";\nimport { multiplyQuantityValue, getAverageValue } from \"./numeric\";\nimport Big from \"big.js\";\nimport {\n isAndGroup,\n isOrGroup,\n isQuantity,\n isValueIntegerLike,\n} from \"../utils/type_guards\";\nimport {\n getDefaultQuantityValue,\n addQuantities,\n deNormalizeQuantity,\n toPlainUnit,\n} from \"./mutations\";\nimport { getUnitRatio, getBaseUnitRatio } from \"../units/conversion\";\nimport {\n findCompatibleQuantityWithinList,\n findListWithCompatibleQuantity,\n} from \"../units/lookup\";\nimport { areUnitsGroupable } from \"../units/compatibility\";\nimport { deepClone } from \"../utils/general\";\n\nexport function getEquivalentUnitsLists(\n ...quantities: (\n | QuantityWithExtendedUnit\n | FlatOrGroup<QuantityWithExtendedUnit>\n )[]\n): QuantityWithUnitDef[][] {\n const quantitiesCopy = deepClone(quantities);\n\n const OrGroups = (\n quantitiesCopy.filter(isOrGroup) as FlatOrGroup<QuantityWithExtendedUnit>[]\n ).filter((q) => q.or.length > 1);\n\n const unitLists: QuantityWithUnitDef[][] = [];\n const normalizeOrGroup = (og: FlatOrGroup<QuantityWithExtendedUnit>) => ({\n ...og,\n or: og.or.map((q) => ({\n ...q,\n unit: resolveUnit(q.unit?.name, q.unit?.integerProtected),\n })),\n });\n\n function findLinkIndexForUnits(\n lists: QuantityWithUnitDef[][],\n unitsToCheck: (UnitDefinitionLike | undefined)[],\n ) {\n return lists.findIndex((l) => {\n const listItems = l.map((q) => resolveUnit(q.unit?.name));\n return unitsToCheck.some(\n (u) => u && listItems.some((lu) => areUnitsGroupable(lu, u)),\n );\n });\n }\n\n function mergeOrGroupIntoList(\n lists: QuantityWithUnitDef[][],\n idx: number,\n og: ReturnType<typeof normalizeOrGroup>,\n ) {\n let unitRatio: Big | undefined;\n const commonUnitList = lists[idx]!.reduce((acc, v) => {\n const normalizedV: QuantityWithUnitDef = {\n ...v,\n unit: resolveUnit(v.unit?.name, v.unit?.integerProtected),\n };\n\n const commonQuantity = og.or.find(\n (q) => isQuantity(q) && areUnitsGroupable(q.unit, normalizedV.unit),\n );\n if (commonQuantity) {\n acc.push(normalizedV);\n // Only set unitRatio on the first match to avoid overwriting with\n // a less precise match (e.g., ambiguous oz matching metric cL)\n if (!unitRatio) {\n unitRatio = getUnitRatio(normalizedV, commonQuantity);\n }\n }\n return acc;\n }, [] as QuantityWithUnitDef[]);\n\n for (const newQ of og.or) {\n if (commonUnitList.some((q) => areUnitsGroupable(q.unit, newQ.unit))) {\n continue;\n } else {\n const scaledQuantity = multiplyQuantityValue(newQ.quantity, unitRatio!);\n lists[idx]!.push({ ...newQ, quantity: scaledQuantity });\n }\n }\n }\n\n for (const orGroup of OrGroups) {\n const orGroupModified = normalizeOrGroup(orGroup);\n const units = orGroupModified.or.map((q) => q.unit);\n const linkIndex = findLinkIndexForUnits(unitLists, units);\n if (linkIndex === -1) {\n unitLists.push(orGroupModified.or);\n } else {\n mergeOrGroupIntoList(unitLists, linkIndex, orGroupModified);\n }\n }\n\n return unitLists;\n}\n\n/**\n * List sorting helper for equivalent units\n * @param list - list of quantities to sort\n * @returns sorted list of quantities with integerProtected units first, then no-unit, then the rest alphabetically\n */\nexport function sortUnitList(list: QuantityWithUnitDef[]) {\n if (!list || list.length <= 1) return list;\n const priorityList: QuantityWithUnitDef[] = [];\n const nonPriorityList: QuantityWithUnitDef[] = [];\n for (const q of list) {\n if (q.unit.integerProtected || q.unit.system === \"none\") {\n priorityList.push(q);\n } else {\n nonPriorityList.push(q);\n }\n }\n\n return priorityList\n .sort((a, b) => {\n const prefixA = a.unit.integerProtected ? \"___\" : \"\";\n const prefixB = b.unit.integerProtected ? \"___\" : \"\";\n return (prefixA + a.unit.name).localeCompare(prefixB + b.unit.name, \"en\");\n })\n .concat(nonPriorityList);\n}\n\nexport function reduceOrsToFirstEquivalent(\n unitList: QuantityWithUnitDef[][],\n quantities: (\n | QuantityWithExtendedUnit\n | FlatOrGroup<QuantityWithExtendedUnit>\n )[],\n): QuantityWithExtendedUnit[] {\n function reduceToQuantity(firstQuantity: QuantityWithExtendedUnit) {\n // Look for the global list of equivalent for this quantity unit;\n const equivalentList = sortUnitList(\n findListWithCompatibleQuantity(unitList, firstQuantity)!,\n );\n if (!equivalentList) return firstQuantity;\n // Find that first quantity in the OR\n const firstQuantityInList = findCompatibleQuantityWithinList(\n equivalentList,\n firstQuantity,\n )!;\n // Normalize the first quantity's unit\n const normalizedFirstQuantity: QuantityWithUnitDef = {\n ...firstQuantity,\n unit: resolveUnit(firstQuantity.unit?.name),\n };\n // Priority 1: the first quantity has an integer-protected unit\n if (firstQuantityInList.unit.integerProtected) {\n const resultQuantity: QuantityWithExtendedUnit = {\n quantity: firstQuantity.quantity,\n };\n /* v8 ignore else -- @preserve */\n if (!isNoUnit(normalizedFirstQuantity.unit)) {\n resultQuantity.unit = { name: normalizedFirstQuantity.unit.name };\n }\n return resultQuantity;\n } else {\n // Priority 2: the next integer-protected units in the equivalent list\n let nextProtected: number | undefined;\n const equivalentListTemp = [...equivalentList];\n while (nextProtected !== -1) {\n nextProtected = equivalentListTemp.findIndex(\n (eq) => eq.unit?.integerProtected,\n );\n // Ratio between the values in the OR group vs the ones used in the equivalent unit list\n if (nextProtected !== -1) {\n const unitRatio = getUnitRatio(\n equivalentListTemp[nextProtected]!,\n firstQuantityInList,\n );\n const nextProtectedQuantityValue = multiplyQuantityValue(\n firstQuantity.quantity,\n unitRatio,\n );\n if (isValueIntegerLike(nextProtectedQuantityValue)) {\n const nextProtectedQuantity: QuantityWithExtendedUnit = {\n quantity: nextProtectedQuantityValue,\n };\n /* v8 ignore else -- @preserve */\n if (!isNoUnit(equivalentListTemp[nextProtected]!.unit)) {\n nextProtectedQuantity.unit = {\n name: equivalentListTemp[nextProtected]!.unit.name,\n };\n }\n\n return nextProtectedQuantity;\n } else {\n equivalentListTemp.splice(nextProtected, 1);\n }\n }\n }\n\n // Priority 3: the first non-integer-Protected value of the list\n const firstNonIntegerProtected = equivalentListTemp.filter(\n (q) => !q.unit.integerProtected,\n )[0]!;\n const unitRatio = getUnitRatio(\n firstNonIntegerProtected,\n firstQuantityInList,\n ).times(getBaseUnitRatio(normalizedFirstQuantity, firstQuantityInList));\n const firstEqQuantity: QuantityWithExtendedUnit = {\n quantity:\n firstNonIntegerProtected.unit.name === firstQuantity.unit!.name\n ? firstQuantity.quantity\n : multiplyQuantityValue(firstQuantity.quantity, unitRatio),\n };\n if (!isNoUnit(firstNonIntegerProtected.unit)) {\n firstEqQuantity.unit = { name: firstNonIntegerProtected.unit.name };\n }\n return firstEqQuantity;\n }\n }\n return quantities.map((q) => {\n if (isQuantity(q)) return reduceToQuantity(q);\n // Now, q is necessarily an OR group\n // We normalize units and sort them to get integerProtected elements first, then no units, then the rest\n const qListModified = sortUnitList(\n q.or.map((qq) => ({\n ...qq,\n unit: resolveUnit(qq.unit?.name, qq.unit?.integerProtected),\n })),\n );\n // We can simply use the first element\n return reduceToQuantity(qListModified[0]!);\n });\n}\n\nexport function addQuantitiesOrGroups(\n quantities: (\n | QuantityWithExtendedUnit\n | FlatOrGroup<QuantityWithExtendedUnit>\n )[],\n system?: SpecificUnitSystem,\n): {\n sum: QuantityWithUnitDef | FlatAndGroup<QuantityWithUnitDef>;\n unitsLists: QuantityWithUnitDef[][];\n} {\n if (quantities.length === 0)\n return {\n sum: {\n quantity: getDefaultQuantityValue(),\n unit: resolveUnit(),\n },\n unitsLists: [],\n };\n // This is purely theoretical and won't really happen in practice\n if (quantities.length === 1) {\n if (isQuantity(quantities[0]!))\n return {\n sum: {\n ...quantities[0],\n unit: resolveUnit(quantities[0].unit?.name),\n },\n unitsLists: [],\n };\n }\n // Step 1: find equivalents units\n const unitsLists = getEquivalentUnitsLists(...quantities);\n // Step 2: reduce the OR group to Quantities\n const reducedQuantities = reduceOrsToFirstEquivalent(unitsLists, quantities);\n // Step 3: calculate the sum\n const sum: QuantityWithUnitDef[] = [];\n for (const nextQ of reducedQuantities) {\n const existingQ = findCompatibleQuantityWithinList(sum, nextQ);\n if (existingQ === undefined) {\n sum.push({\n ...nextQ,\n unit: resolveUnit(nextQ.unit?.name),\n });\n } else {\n const sumQ = addQuantities(existingQ, nextQ, system);\n existingQ.quantity = sumQ.quantity;\n existingQ.unit = resolveUnit(sumQ.unit?.name);\n }\n }\n if (sum.length === 1) {\n return { sum: sum[0]!, unitsLists };\n }\n return { sum: { and: sum }, unitsLists };\n}\n\nexport function regroupQuantitiesAndExpandEquivalents(\n sum: QuantityWithUnitDef | FlatAndGroup<QuantityWithUnitDef>,\n unitsLists: QuantityWithUnitDef[][],\n system?: SpecificUnitSystem,\n): (QuantityWithExtendedUnit | MaybeNestedOrGroup<QuantityWithExtendedUnit>)[] {\n const sumQuantities = isAndGroup(sum) ? sum.and : [sum];\n const result: (\n | QuantityWithExtendedUnit\n | MaybeNestedOrGroup<QuantityWithExtendedUnit>\n )[] = [];\n const processedQuantities = new Set<QuantityWithUnitDef>();\n\n for (const list of unitsLists) {\n const listCopy = deepClone(list);\n const main: QuantityWithUnitDef[] = [];\n const mainCandidates = sumQuantities.filter(\n (q) => !processedQuantities.has(q),\n );\n if (mainCandidates.length === 0) continue;\n\n mainCandidates.forEach((q) => {\n // If the sum contain a value from the unit list, we push it to the mains and remove it from the list\n const mainInList = findCompatibleQuantityWithinList(listCopy, q);\n /* v8 ignore else -- @preserve */\n if (mainInList !== undefined) {\n processedQuantities.add(q);\n main.push(q);\n listCopy.splice(listCopy.indexOf(mainInList), 1);\n }\n });\n\n // We sort the equivalent units and calculate the equivalent value for each of them\n const equivalents = sortUnitList(listCopy).map((equiv) => {\n const initialValue: QuantityWithExtendedUnit = {\n quantity: getDefaultQuantityValue(),\n };\n /* v8 ignore else -- @preserve */\n if (equiv.unit) {\n initialValue.unit = { name: equiv.unit.name };\n }\n return main.reduce((acc, v) => {\n const mainInList = findCompatibleQuantityWithinList(list, v)!;\n // If the sum unit differs from the original list unit (e.g., cups → gallon after best-unit upgrade),\n // we need to convert the sum value to the equivalent amount in the original unit before scaling.\n const conversionRatio = getBaseUnitRatio(v, mainInList);\n const valueInOriginalUnit = Big(getAverageValue(v.quantity)).times(\n conversionRatio,\n );\n const newValue: QuantityWithExtendedUnit = {\n quantity: multiplyQuantityValue(\n {\n type: \"fixed\",\n value: {\n type: \"decimal\",\n decimal: valueInOriginalUnit.toNumber(),\n },\n },\n Big(getAverageValue(equiv.quantity)).div(\n getAverageValue(mainInList.quantity),\n ),\n ),\n };\n if (equiv.unit && !isNoUnit(equiv.unit)) {\n newValue.unit = { name: equiv.unit.name };\n }\n return addQuantities(acc, newValue, system);\n }, initialValue);\n });\n\n if (main.length + equivalents.length > 1) {\n const resultMain:\n | QuantityWithExtendedUnit\n | FlatAndGroup<QuantityWithExtendedUnit> =\n main.length > 1\n ? {\n and: main.map(deNormalizeQuantity),\n }\n : deNormalizeQuantity(main[0]!);\n result.push({\n or: [resultMain, ...equivalents],\n });\n }\n // Processing a UnitList with only 1 quantity is purely theoretical and won't happen in practice\n else {\n result.push(deNormalizeQuantity(main[0]!));\n }\n }\n\n // We add at the end the lone quantities\n sumQuantities\n .filter((q) => !processedQuantities.has(q))\n .forEach((q) => result.push(deNormalizeQuantity(q)));\n\n return result;\n}\n\nexport function addEquivalentsAndSimplify(\n quantities: (\n | QuantityWithExtendedUnit\n | FlatOrGroup<QuantityWithExtendedUnit>\n )[],\n system?: SpecificUnitSystem,\n): QuantityWithPlainUnit | MaybeNestedGroup<QuantityWithPlainUnit> {\n if (quantities.length === 1) {\n return toPlainUnit(quantities[0]!);\n }\n // Step 1+2+3: find equivalents, reduce groups and add quantities\n const { sum, unitsLists } = addQuantitiesOrGroups(quantities, system);\n // Step 4: regroup and expand equivalents per group\n const regrouped = regroupQuantitiesAndExpandEquivalents(\n sum,\n unitsLists,\n system,\n );\n if (regrouped.length === 1) {\n return toPlainUnit(regrouped[0]!);\n } else {\n return { and: regrouped.map(toPlainUnit) };\n }\n}\n","import type { QuantityWithExtendedUnit, QuantityWithUnitDef } from \"../types\";\nimport { resolveUnit } from \"./definitions\";\nimport { areUnitsGroupable } from \"./compatibility\";\n\nexport function findListWithCompatibleQuantity(\n list: QuantityWithUnitDef[][],\n quantity: QuantityWithExtendedUnit,\n) {\n const quantityWithUnitDef = {\n ...quantity,\n unit: resolveUnit(quantity.unit?.name),\n };\n return list.find((l) =>\n l.some((lq) => areUnitsGroupable(lq.unit, quantityWithUnitDef.unit)),\n );\n}\n\nexport function findCompatibleQuantityWithinList(\n list: QuantityWithUnitDef[],\n quantity: QuantityWithExtendedUnit,\n): QuantityWithUnitDef | undefined {\n const quantityWithUnitDef = {\n ...quantity,\n unit: resolveUnit(quantity.unit?.name),\n };\n return list.find(\n (q) =>\n q.unit.name === quantityWithUnitDef.unit.name ||\n (q.unit.type === quantityWithUnitDef.unit.type &&\n q.unit.type !== \"other\"),\n );\n}\n","const legacyDeepClone = <T>(v: T): T => {\n if (v === null || typeof v !== \"object\") {\n return v;\n }\n if (v instanceof Map) {\n return new Map(\n Array.from(v.entries()).map(([k, val]) => [\n legacyDeepClone(k),\n legacyDeepClone(val),\n ])\n ) as T;\n }\n if (v instanceof Set) {\n return new Set(Array.from(v).map((val: unknown) => legacyDeepClone(val))) as T;\n }\n if (v instanceof Date) {\n return new Date(v.getTime()) as T;\n }\n if (Array.isArray(v)) {\n return v.map((item: unknown) => legacyDeepClone(item)) as T;\n }\n const cloned = {} as Record<string, unknown>;\n for (const key of Object.keys(v)) {\n cloned[key] = legacyDeepClone((v as Record<string, unknown>)[key]);\n }\n return cloned as T;\n};\n\nexport const deepClone = <T>(v: T): T =>\n typeof structuredClone === \"function\"\n ? structuredClone(v)\n : legacyDeepClone(v);\n","import type {\n Metadata,\n Ingredient,\n IngredientExtras,\n IngredientItem,\n Timer,\n Step,\n NoteItem,\n Cookware,\n MetadataExtract,\n CookwareItem,\n IngredientFlag,\n CookwareFlag,\n RecipeAlternatives,\n IngredientAlternative,\n FlatOrGroup,\n QuantityWithExtendedUnit,\n AlternativeIngredientRef,\n QuantityWithPlainUnit,\n IngredientQuantityGroup,\n IngredientQuantityAndGroup,\n ArbitraryScalable,\n FixedNumericValue,\n StepItem,\n GetIngredientQuantitiesOptions,\n SpecificUnitSystem,\n Unit,\n MaybeScalableQuantity,\n} from \"../types\";\nimport { Section } from \"./section\";\nimport {\n tokensRegex,\n commentRegex,\n blockCommentRegex,\n metadataRegex,\n ingredientWithAlternativeRegex,\n ingredientWithGroupKeyRegex,\n ingredientAliasRegex,\n floatRegex,\n quantityAlternativeRegex,\n inlineIngredientAlternativesRegex,\n arbitraryScalableRegex,\n} from \"../regex\";\nimport {\n flushPendingItems,\n flushPendingNote,\n findAndUpsertIngredient,\n findAndUpsertCookware,\n parseQuantityInput,\n extractMetadata,\n unionOfSets,\n getAlternativeSignature,\n} from \"../utils/parser_helpers\";\nimport { addEquivalentsAndSimplify } from \"../quantities/alternatives\";\nimport { multiplyQuantityValue } from \"../quantities/numeric\";\nimport {\n toPlainUnit,\n toExtendedUnit,\n flattenPlainUnitGroup,\n convertQuantityToSystem,\n applyBestUnit,\n} from \"../quantities/mutations\";\nimport { resolveUnit } from \"../units/definitions\";\nimport { isUnitCompatibleWithSystem } from \"../units/compatibility\";\nimport Big from \"big.js\";\nimport { deepClone } from \"../utils/general\";\nimport { InvalidQuantityFormat } from \"../errors\";\n\n/**\n * Recipe parser.\n *\n * ## Usage\n *\n * You can either directly provide the recipe string when creating the instance\n * e.g. `const recipe = new Recipe('Add @eggs{3}')`, or create it first and then pass\n * the recipe string to the {@link Recipe.parse | parse()} method.\n *\n * Look at the [properties](#properties) to see how the recipe's properties are parsed.\n *\n * @category Classes\n *\n * @example\n * ```typescript\n * import { Recipe } from \"@tmlmt/cooklang-parser\";\n *\n * const recipeString = `\n * ---\n * title: Pancakes\n * tags: [breakfast, easy]\n * ---\n * Crack the @eggs{3} with @flour{100%g} and @milk{200%mL}\n *\n * Melt some @butter{50%g} in a #pan on medium heat.\n *\n * Cook for ~{5%minutes} on each side.\n * `\n * const recipe = new Recipe(recipeString);\n * ```\n */\nexport class Recipe {\n /**\n * The parsed recipe metadata.\n */\n metadata: Metadata = {};\n /**\n * The possible choices of alternative ingredients for this recipe.\n */\n choices: RecipeAlternatives = {\n ingredientItems: new Map(),\n ingredientGroups: new Map(),\n };\n /**\n * The parsed recipe ingredients.\n */\n ingredients: Ingredient[] = [];\n /**\n * The parsed recipe sections.\n */\n sections: Section[] = [];\n /**\n * The parsed recipe cookware.\n */\n cookware: Cookware[] = [];\n /**\n * The parsed recipe timers.\n */\n timers: Timer[] = [];\n /**\n * The parsed arbitrary quantities.\n */\n arbitraries: ArbitraryScalable[] = [];\n /**\n * The parsed recipe servings. Used for scaling. Parsed from one of\n * {@link Metadata.servings}, {@link Metadata.yield} or {@link Metadata.serves}\n * metadata fields.\n *\n * @see {@link Recipe.scaleBy | scaleBy()} and {@link Recipe.scaleTo | scaleTo()} methods\n */\n servings?: number;\n\n /**\n * Gets the unit system specified in the recipe metadata.\n * Used for resolving ambiguous units like tsp, tbsp, cup, etc.\n *\n * @returns The unit system if specified, or undefined to use defaults\n */\n get unitSystem(): SpecificUnitSystem | undefined {\n return Recipe.unitSystems.get(this);\n }\n\n /**\n * External storage for unit system (not a property on instances).\n * Used for resolving ambiguous units during quantity addition.\n */\n private static unitSystems = new WeakMap<Recipe, SpecificUnitSystem>();\n\n /**\n * External storage for item count (not a property on instances).\n * Used for giving ID numbers to items during parsing.\n */\n private static itemCounts = new WeakMap<Recipe, number>();\n\n /**\n * Gets the current item count for this recipe.\n */\n private getItemCount(): number {\n return Recipe.itemCounts.get(this)!;\n }\n\n /**\n * Gets the current item count and increments it.\n */\n private getAndIncrementItemCount(): number {\n const current = this.getItemCount();\n Recipe.itemCounts.set(this, current + 1);\n return current;\n }\n\n /**\n * Creates a new Recipe instance.\n * @param content - The recipe content to parse.\n */\n constructor(content?: string) {\n Recipe.itemCounts.set(this, 0);\n if (content) {\n this.parse(content);\n }\n }\n\n /**\n * Parses a matched arbitrary scalable quantity and adds it to the given array.\n * @private\n * @param regexMatchGroups - The regex match groups from arbitrary scalable regex.\n * @param intoArray - The array to push the parsed arbitrary scalable item into.\n */\n private _parseArbitraryScalable(\n regexMatchGroups: RegExpMatchArray[\"groups\"],\n intoArray: Array<NoteItem | StepItem>,\n ): void {\n // Type-guard to ensure regexMatchGroups is defined, which it is when calling this function\n // v8 ignore if -- @preserve\n if (!regexMatchGroups || !regexMatchGroups.arbitraryQuantity) return;\n const quantityMatch = regexMatchGroups.arbitraryQuantity\n ?.trim()\n .match(quantityAlternativeRegex);\n // Type-guard to ensure quantityMatch.groups is defined, which we know when calling this function\n // v8 ignore else -- @preserve\n if (quantityMatch?.groups) {\n const value = parseQuantityInput(quantityMatch.groups.quantity!);\n const unit = quantityMatch.groups.unit;\n const name = regexMatchGroups.arbitraryName || undefined;\n if (!value || (value.type === \"fixed\" && value.value.type === \"text\")) {\n throw new InvalidQuantityFormat(\n regexMatchGroups.arbitraryQuantity?.trim(),\n \"Arbitrary quantities must have a numerical value\",\n );\n }\n const arbitrary: ArbitraryScalable = {\n quantity: value as FixedNumericValue,\n };\n if (name) arbitrary.name = name;\n if (unit) arbitrary.unit = unit;\n intoArray.push({\n type: \"arbitrary\",\n index: this.arbitraries.push(arbitrary) - 1,\n });\n }\n }\n\n /**\n * Parses text for arbitrary scalables and returns NoteItem array.\n * @param text - The text to parse for arbitrary scalables.\n * @returns Array of NoteItem (text and arbitrary scalable items).\n */\n private _parseNoteText(text: string): NoteItem[] {\n const noteItems: NoteItem[] = [];\n let cursor = 0;\n const globalRegex = new RegExp(arbitraryScalableRegex.source, \"g\");\n\n for (const match of text.matchAll(globalRegex)) {\n const idx = match.index;\n /* v8 ignore else -- @preserve */\n if (idx > cursor) {\n noteItems.push({ type: \"text\", value: text.slice(cursor, idx) });\n }\n\n this._parseArbitraryScalable(match.groups, noteItems);\n cursor = idx + match[0].length;\n }\n\n if (cursor < text.length) {\n noteItems.push({ type: \"text\", value: text.slice(cursor) });\n }\n\n return noteItems;\n }\n\n private _parseQuantityRecursive(\n quantityRaw: string,\n ): QuantityWithExtendedUnit[] {\n let quantityMatch = quantityRaw.match(quantityAlternativeRegex);\n const quantities: QuantityWithExtendedUnit[] = [];\n while (quantityMatch?.groups) {\n const value = quantityMatch.groups.quantity\n ? parseQuantityInput(quantityMatch.groups.quantity)\n : undefined;\n const unit = quantityMatch.groups.unit;\n if (value) {\n const newQuantity: QuantityWithExtendedUnit = { quantity: value };\n if (unit) {\n if (unit.startsWith(\"=\")) {\n newQuantity.unit = {\n name: unit.substring(1),\n integerProtected: true,\n };\n } else {\n newQuantity.unit = { name: unit };\n }\n }\n quantities.push(newQuantity);\n } else {\n throw new InvalidQuantityFormat(quantityRaw);\n }\n quantityMatch = quantityMatch.groups.alternative\n ? quantityMatch.groups.alternative.match(quantityAlternativeRegex)\n : null;\n }\n return quantities;\n }\n\n private _parseIngredientWithAlternativeRecursive(\n ingredientMatchString: string,\n items: Step[\"items\"],\n ): void {\n const alternatives: IngredientAlternative[] = [];\n let testString = ingredientMatchString;\n while (true) {\n const match = testString.match(\n alternatives.length > 0\n ? inlineIngredientAlternativesRegex\n : ingredientWithAlternativeRegex,\n );\n if (!match?.groups) break;\n const groups = match.groups;\n\n // Use variables for readability\n // @<modifiers><name>{quantity%unit|altQuantities}(preparation)[note]|<altIngredients>\n let name = (groups.mIngredientName || groups.sIngredientName)!;\n\n // 1. We build up the different parts of the Ingredient object\n // Preparation\n const preparation = groups.ingredientPreparation;\n // Flags\n const modifiers = groups.ingredientModifiers;\n const reference = modifiers !== undefined && modifiers.includes(\"&\");\n const flags: IngredientFlag[] = [];\n if (modifiers !== undefined && modifiers.includes(\"?\")) {\n flags.push(\"optional\");\n }\n if (modifiers !== undefined && modifiers.includes(\"-\")) {\n flags.push(\"hidden\");\n }\n if (\n (modifiers !== undefined && modifiers.includes(\"@\")) ||\n groups.ingredientRecipeAnchor\n ) {\n flags.push(\"recipe\");\n }\n // Extras\n let extras: IngredientExtras | undefined = undefined;\n // -- if the ingredient is a recipe, we need to extract the name from the path given\n if (flags.includes(\"recipe\")) {\n extras = { path: `${name}.cook` };\n name = name.substring(name.lastIndexOf(\"/\") + 1);\n }\n // Distinguish name from display name / name alias\n const aliasMatch = name.match(ingredientAliasRegex);\n let listName, displayName: string;\n if (\n aliasMatch &&\n aliasMatch.groups!.ingredientListName!.trim().length > 0 &&\n aliasMatch.groups!.ingredientDisplayName!.trim().length > 0\n ) {\n listName = aliasMatch.groups!.ingredientListName!.trim();\n displayName = aliasMatch.groups!.ingredientDisplayName!.trim();\n } else {\n listName = name;\n displayName = name;\n }\n\n const newIngredient: Ingredient = {\n name: listName,\n };\n // Only add parameters if they are non null / non empty\n if (preparation) {\n newIngredient.preparation = preparation;\n }\n if (flags.length > 0) {\n newIngredient.flags = flags;\n }\n if (extras) {\n newIngredient.extras = extras;\n }\n\n const idxInList = findAndUpsertIngredient(\n this.ingredients,\n newIngredient,\n reference,\n );\n\n // 2. We build up the ingredient item\n // -- alternative quantities\n let itemQuantity: MaybeScalableQuantity | undefined = undefined;\n if (groups.ingredientQuantity) {\n const parsedQuantities = this._parseQuantityRecursive(\n groups.ingredientQuantity,\n );\n const [primary, ...rest] = parsedQuantities;\n if (primary) {\n itemQuantity = {\n ...primary,\n scalable: groups.ingredientQuantityModifier !== \"=\",\n };\n if (rest.length > 0) {\n itemQuantity.equivalents = rest;\n }\n }\n }\n\n const alternative: IngredientAlternative = {\n index: idxInList,\n displayName,\n };\n // Only add quantity fields and note if they exist\n const note = groups.ingredientNote?.trim();\n if (note) {\n alternative.note = note;\n }\n if (itemQuantity) {\n Object.assign(alternative, itemQuantity);\n }\n alternatives.push(alternative);\n testString = groups.ingredientAlternative || \"\";\n }\n\n // Update alternatives list of all processed ingredients\n if (alternatives.length > 1) {\n const alternativesIndexes = alternatives.map((alt) => alt.index);\n for (const ingredientIndex of alternativesIndexes) {\n const ingredient = this.ingredients[ingredientIndex];\n // In practice, the ingredient will always be found\n /* v8 ignore else -- @preserve */\n if (ingredient) {\n if (!ingredient.alternatives) {\n ingredient.alternatives = new Set(\n alternativesIndexes.filter((index) => index !== ingredientIndex),\n );\n } else {\n ingredient.alternatives = unionOfSets(\n ingredient.alternatives,\n new Set(\n alternativesIndexes.filter(\n (index) => index !== ingredientIndex,\n ),\n ),\n );\n }\n }\n }\n }\n\n const id = `ingredient-item-${this.getAndIncrementItemCount()}`;\n\n // Finalize item\n const newItem: IngredientItem = {\n type: \"ingredient\",\n id,\n alternatives,\n };\n items.push(newItem);\n\n if (alternatives.length > 1) {\n this.choices.ingredientItems.set(id, alternatives);\n }\n }\n\n private _parseIngredientWithGroupKey(\n ingredientMatchString: string,\n items: Step[\"items\"],\n ): void {\n const match = ingredientMatchString.match(ingredientWithGroupKeyRegex);\n // This is a type guard to ensure match and match.groups are defined\n /* v8 ignore if -- @preserve */\n if (!match?.groups) return;\n const groups = match.groups;\n\n // Use variables for readability\n // @|<groupKey|<modifiers><name>{quantity%unit|altQuantities}(preparation)[note]\n const groupKey = groups.gIngredientGroupKey!;\n let name = (groups.gmIngredientName || groups.gsIngredientName)!;\n\n // 1. We build up the different parts of the Ingredient object\n // Preparation\n const preparation = groups.gIngredientPreparation;\n // Flags\n const modifiers = groups.gIngredientModifiers;\n const reference = modifiers !== undefined && modifiers.includes(\"&\");\n const flags: IngredientFlag[] = [];\n if (modifiers !== undefined && modifiers.includes(\"?\")) {\n flags.push(\"optional\");\n }\n if (modifiers !== undefined && modifiers.includes(\"-\")) {\n flags.push(\"hidden\");\n }\n if (\n (modifiers !== undefined && modifiers.includes(\"@\")) ||\n groups.gIngredientRecipeAnchor\n ) {\n flags.push(\"recipe\");\n }\n // Extras\n let extras: IngredientExtras | undefined = undefined;\n // -- if the ingredient is a recipe, we need to extract the name from the path given\n if (flags.includes(\"recipe\")) {\n extras = { path: `${name}.cook` };\n name = name.substring(name.lastIndexOf(\"/\") + 1);\n }\n // Distinguish name from display name / name alias\n const aliasMatch = name.match(ingredientAliasRegex);\n let listName, displayName: string;\n if (\n aliasMatch &&\n aliasMatch.groups!.ingredientListName!.trim().length > 0 &&\n aliasMatch.groups!.ingredientDisplayName!.trim().length > 0\n ) {\n listName = aliasMatch.groups!.ingredientListName!.trim();\n displayName = aliasMatch.groups!.ingredientDisplayName!.trim();\n } else {\n listName = name;\n displayName = name;\n }\n\n const newIngredient: Ingredient = {\n name: listName,\n };\n // Only add parameters if they are non null / non empty\n if (preparation) {\n newIngredient.preparation = preparation;\n }\n if (flags.length > 0) {\n newIngredient.flags = flags;\n }\n if (extras) {\n newIngredient.extras = extras;\n }\n\n const idxInList = findAndUpsertIngredient(\n this.ingredients,\n newIngredient,\n reference,\n );\n\n // 2. We build up the ingredient item\n // -- alternative quantities\n let itemQuantity: MaybeScalableQuantity | undefined = undefined;\n if (groups.gIngredientQuantity) {\n const parsedQuantities = this._parseQuantityRecursive(\n groups.gIngredientQuantity,\n );\n const [primary, ...rest] = parsedQuantities;\n itemQuantity = {\n ...primary!, // there's necessarily a primary quantity as the match group was detected\n scalable: groups.gIngredientQuantityModifier !== \"=\",\n };\n if (rest.length > 0) {\n itemQuantity.equivalents = rest;\n }\n }\n\n const alternative: IngredientAlternative = {\n index: idxInList,\n displayName,\n };\n // Only add quantity fields if it exists\n if (itemQuantity) {\n Object.assign(alternative, itemQuantity);\n }\n\n const existingAlternatives = this.choices.ingredientGroups.get(groupKey);\n // For all alternative ingredients already processed for this group, add the new ingredient as alternative\n function upsertAlternativeToIngredient(\n ingredients: Ingredient[],\n ingredientIdx: number,\n newAlternativeIdx: number,\n ) {\n const ingredient = ingredients[ingredientIdx];\n // In practice, the ingredient will always be found\n /* v8 ignore else -- @preserve */\n if (ingredient) {\n if (ingredient.alternatives === undefined) {\n ingredient.alternatives = new Set([newAlternativeIdx]);\n } else {\n ingredient.alternatives.add(newAlternativeIdx);\n }\n }\n }\n if (existingAlternatives) {\n for (const alt of existingAlternatives) {\n upsertAlternativeToIngredient(this.ingredients, alt.index, idxInList);\n upsertAlternativeToIngredient(this.ingredients, idxInList, alt.index);\n }\n }\n const id = `ingredient-item-${this.getAndIncrementItemCount()}`;\n\n // Finalize item\n const newItem: IngredientItem = {\n type: \"ingredient\",\n id,\n group: groupKey,\n alternatives: [alternative],\n };\n items.push(newItem);\n\n // Populate or update choices\n const choiceAlternative = deepClone(alternative);\n choiceAlternative.itemId = id;\n const existingChoice = this.choices.ingredientGroups.get(groupKey);\n if (!existingChoice) {\n this.choices.ingredientGroups.set(groupKey, [choiceAlternative]);\n } else {\n existingChoice.push(choiceAlternative);\n }\n }\n\n /**\n * Populates the `quantities` property for each ingredient based on\n * how they appear in the recipe preparation. Only primary ingredients\n * get quantities populated. Primary ingredients get `usedAsPrimary: true` flag.\n *\n * For inline alternatives (e.g. `\\@a|b|c`), the first alternative is primary.\n * For grouped alternatives (e.g. `\\@|group|a`, `\\@|group|b`), the first item in the group is primary.\n *\n * Quantities are grouped by their alternative signature and summed using addEquivalentsAndSimplify.\n * @internal\n */\n private _populate_ingredient_quantities(): void {\n // Reset quantities and usedAsPrimary flag\n for (const ing of this.ingredients) {\n delete ing.quantities;\n delete ing.usedAsPrimary;\n }\n\n // Get ingredients with quantities using default (no explicit choice = primary with alternatives)\n const ingredientsWithQuantities = this.getIngredientQuantities();\n\n // Track which indices have been matched (for handling duplicate names)\n const matchedIndices = new Set<number>();\n\n // Copy quantities and usedAsPrimary to this.ingredients\n // Match by finding the first ingredient with same name that hasn't been matched yet\n for (const computed of ingredientsWithQuantities) {\n const idx = this.ingredients.findIndex(\n (ing, i) => ing.name === computed.name && !matchedIndices.has(i),\n );\n matchedIndices.add(idx);\n const ing = this.ingredients[idx]!;\n if (computed.quantities) {\n ing.quantities = computed.quantities;\n }\n if (computed.usedAsPrimary) {\n ing.usedAsPrimary = true;\n }\n }\n }\n\n /**\n * Gets ingredients with their quantities populated, optionally filtered by section/step\n * and respecting user choices for alternatives.\n *\n * When no options are provided, returns all recipe ingredients with quantities\n * calculated using primary alternatives (same as after parsing).\n *\n * @param options - Options for filtering and choice selection:\n * - `section`: Filter to a specific section (Section object or 0-based index)\n * - `step`: Filter to a specific step (Step object or 0-based index)\n * - `choices`: Choices for alternative ingredients (defaults to primary)\n * @returns Array of Ingredient objects with quantities populated\n *\n * @example\n * ```typescript\n * // Get all ingredients with primary alternatives\n * const ingredients = recipe.getIngredientQuantities();\n *\n * // Get ingredients for a specific section\n * const sectionIngredients = recipe.getIngredientQuantities({ section: 0 });\n *\n * // Get ingredients with specific choices applied\n * const withChoices = recipe.getIngredientQuantities({\n * choices: { ingredientItems: new Map([['ingredient-item-2', 1]]) }\n * });\n * ```\n */\n getIngredientQuantities(\n options?: GetIngredientQuantitiesOptions,\n ): Ingredient[] {\n const { section, step, choices } = options || {};\n\n // Determine sections to process\n const sectionsToProcess =\n section !== undefined\n ? (() => {\n const idx =\n typeof section === \"number\"\n ? section\n : this.sections.indexOf(section);\n return idx >= 0 && idx < this.sections.length\n ? [this.sections[idx]!]\n : [];\n })()\n : this.sections;\n\n // Type for accumulated quantities\n type QuantityAccumulator = {\n quantities: (\n | QuantityWithExtendedUnit\n | FlatOrGroup<QuantityWithExtendedUnit>\n )[];\n alternativeQuantities: Map<\n number,\n (QuantityWithExtendedUnit | FlatOrGroup<QuantityWithExtendedUnit>)[]\n >;\n };\n\n // Map: ingredientIndex -> alternativeSignature -> accumulated data\n const ingredientGroups = new Map<\n number,\n Map<string | null, QuantityAccumulator>\n >();\n\n // Track selected ingredients (get quantities + usedAsPrimary) and all referenced ingredients\n const selectedIndices = new Set<number>();\n const referencedIndices = new Set<number>();\n\n for (const currentSection of sectionsToProcess) {\n const allSteps = currentSection.content.filter(\n (item): item is Step => item.type === \"step\",\n );\n\n // Determine steps to process\n const stepsToProcess =\n step === undefined\n ? allSteps\n : typeof step === \"number\"\n ? step >= 0 && step < allSteps.length\n ? [allSteps[step]!]\n : []\n : allSteps.includes(step)\n ? [step]\n : [];\n\n for (const currentStep of stepsToProcess) {\n for (const item of currentStep.items.filter(\n (item): item is IngredientItem => item.type === \"ingredient\",\n )) {\n const isGrouped = \"group\" in item && item.group !== undefined;\n const groupAlternatives = isGrouped\n ? this.choices.ingredientGroups.get(item.group!)\n : undefined;\n\n // Determine selection state\n let selectedAltIndex = 0;\n let isSelected = false;\n let hasExplicitChoice = false;\n\n if (isGrouped) {\n const groupChoice = choices?.ingredientGroups?.get(item.group!);\n hasExplicitChoice = groupChoice !== undefined;\n const targetIndex = groupChoice ?? 0;\n isSelected = groupAlternatives?.[targetIndex]?.itemId === item.id;\n } else {\n const itemChoice = choices?.ingredientItems?.get(item.id);\n hasExplicitChoice = itemChoice !== undefined;\n selectedAltIndex = itemChoice ?? 0;\n isSelected = true;\n }\n\n const alternative = item.alternatives[selectedAltIndex];\n if (!alternative || !isSelected) continue;\n\n selectedIndices.add(alternative.index);\n\n // Add all alternatives to referenced set (so indices remain valid in result)\n const allAlts = isGrouped ? groupAlternatives! : item.alternatives;\n for (const alt of allAlts) {\n referencedIndices.add(alt.index);\n }\n\n if (!alternative.quantity) continue;\n\n // Build quantity entry with equivalents\n const baseQty: QuantityWithExtendedUnit = {\n quantity: alternative.quantity,\n ...(alternative.unit && {\n unit: alternative.unit,\n }),\n };\n const quantityEntry = alternative.equivalents?.length\n ? { or: [baseQty, ...alternative.equivalents] }\n : baseQty;\n\n // Build alternative refs (only when no explicit choice)\n let alternativeRefs: AlternativeIngredientRef[] | undefined;\n if (!hasExplicitChoice && allAlts.length > 1) {\n alternativeRefs = allAlts\n .filter((alt) =>\n isGrouped\n ? alt.itemId !== item.id\n : alt.index !== alternative.index,\n )\n .map((otherAlt) => {\n const ref: AlternativeIngredientRef = { index: otherAlt.index };\n if (otherAlt.quantity) {\n const altQty: QuantityWithPlainUnit = {\n quantity: otherAlt.quantity,\n ...(otherAlt.unit && {\n unit: otherAlt.unit.name,\n }),\n ...(otherAlt.equivalents && {\n equivalents: otherAlt.equivalents.map(\n (eq) => toPlainUnit(eq) as QuantityWithPlainUnit,\n ),\n }),\n };\n ref.quantities = [altQty];\n }\n return ref;\n });\n }\n\n // Get or create accumulator for this ingredient/signature\n // Use unit type+system for signature only when there are alternatives,\n // so compatible units (g/kg) group together but incompatible (cup/g) stay separate\n const altIndices = getAlternativeSignature(alternativeRefs) ?? \"\";\n let signature: string | null;\n if (isGrouped) {\n const resolvedUnit = resolveUnit(alternative.unit?.name);\n signature = `group:${item.group}|${altIndices}|${resolvedUnit.type}`;\n } else if (altIndices) {\n // Has alternatives: include unit type to keep incompatible units separate\n const resolvedUnit = resolveUnit(alternative.unit?.name);\n signature = `${altIndices}|${resolvedUnit.type}}`;\n } else {\n // No alternatives: use null to allow normal summing behavior\n signature = null;\n }\n\n if (!ingredientGroups.has(alternative.index)) {\n ingredientGroups.set(alternative.index, new Map());\n }\n const groupsForIng = ingredientGroups.get(alternative.index)!;\n if (!groupsForIng.has(signature)) {\n groupsForIng.set(signature, {\n quantities: [],\n alternativeQuantities: new Map(),\n });\n }\n const group = groupsForIng.get(signature)!;\n\n group.quantities.push(quantityEntry);\n\n // Accumulate alternative quantities\n for (const ref of alternativeRefs ?? []) {\n if (!group.alternativeQuantities.has(ref.index)) {\n group.alternativeQuantities.set(ref.index, []);\n }\n for (const altQty of ref.quantities ?? []) {\n const extended = toExtendedUnit({\n quantity: altQty.quantity,\n unit: altQty.unit,\n });\n if (altQty.equivalents?.length) {\n const eqEntries: QuantityWithExtendedUnit[] = [\n extended,\n ...altQty.equivalents.map((eq) => toExtendedUnit(eq)),\n ];\n group.alternativeQuantities\n .get(ref.index)!\n .push({ or: eqEntries });\n } else {\n group.alternativeQuantities.get(ref.index)!.push(extended);\n }\n }\n }\n }\n }\n }\n\n // Build result\n const result: Ingredient[] = [];\n\n for (let index = 0; index < this.ingredients.length; index++) {\n if (!referencedIndices.has(index)) continue;\n\n const orig = this.ingredients[index]!;\n const ing: Ingredient = {\n name: orig.name,\n ...(orig.preparation && { preparation: orig.preparation }),\n ...(orig.flags && { flags: orig.flags }),\n ...(orig.extras && { extras: orig.extras }),\n };\n\n if (selectedIndices.has(index)) {\n ing.usedAsPrimary = true;\n\n const groupsForIng = ingredientGroups.get(index);\n if (groupsForIng) {\n const quantityGroups: (\n | IngredientQuantityGroup\n | IngredientQuantityAndGroup\n )[] = [];\n\n for (const [, group] of groupsForIng) {\n const summed = addEquivalentsAndSimplify(\n group.quantities,\n this.unitSystem,\n );\n const flattened = flattenPlainUnitGroup(summed);\n\n // Build alternatives from accumulated quantities\n const alternatives: AlternativeIngredientRef[] | undefined =\n group.alternativeQuantities.size > 0\n ? [...group.alternativeQuantities].map(([altIdx, altQtys]) => ({\n index: altIdx,\n ...(altQtys.length > 0 && {\n quantities: flattenPlainUnitGroup(\n addEquivalentsAndSimplify(altQtys, this.unitSystem),\n ).flatMap(\n /* v8 ignore next -- item.and branch requires complex nested AND-with-equivalents structure */\n (item) => (\"quantity\" in item ? [item] : item.and),\n ),\n }),\n }))\n : undefined;\n\n for (const gq of flattened) {\n if (\"and\" in gq) {\n quantityGroups.push({\n and: gq.and,\n ...(gq.equivalents?.length && {\n equivalents: gq.equivalents,\n }),\n ...(alternatives?.length && { alternatives }),\n });\n } else {\n quantityGroups.push({\n ...(gq as IngredientQuantityGroup),\n ...(alternatives?.length && { alternatives }),\n });\n }\n }\n }\n\n // v8 ignore else -- @preserve\n if (quantityGroups.length > 0) {\n ing.quantities = quantityGroups;\n }\n }\n }\n\n result.push(ing);\n }\n\n return result;\n }\n\n /**\n * Parses a recipe from a string.\n * @param content - The recipe content to parse.\n */\n parse(content: string) {\n // Remove noise\n const cleanContent = content\n .replace(metadataRegex, \"\")\n .replace(commentRegex, \"\")\n .replace(blockCommentRegex, \"\")\n .trim()\n .split(/\\r\\n?|\\n/);\n\n // Metadata\n const { metadata, servings, unitSystem }: MetadataExtract =\n extractMetadata(content);\n this.metadata = metadata;\n this.servings = servings;\n if (unitSystem) Recipe.unitSystems.set(this, unitSystem);\n\n // Initializing utility variables and property bearers\n let blankLineBefore = true;\n let section: Section = new Section();\n const items: Step[\"items\"] = [];\n let noteText = \"\";\n let inNote = false;\n\n // We parse content line by line\n for (const line of cleanContent) {\n // A blank line triggers flushing pending stuff\n if (line.trim().length === 0) {\n flushPendingItems(section, items);\n flushPendingNote(\n section,\n noteText ? this._parseNoteText(noteText) : [],\n );\n noteText = \"\";\n blankLineBefore = true;\n inNote = false;\n continue;\n }\n\n // New section\n if (line.startsWith(\"=\")) {\n flushPendingItems(section, items);\n flushPendingNote(\n section,\n noteText ? this._parseNoteText(noteText) : [],\n );\n noteText = \"\";\n\n if (this.sections.length === 0 && section.isBlank()) {\n section.name = line.replace(/^=+|=+$/g, \"\").trim();\n } else {\n /* v8 ignore else -- @preserve */\n if (!section.isBlank()) {\n this.sections.push(section);\n }\n section = new Section(line.replace(/^=+|=+$/g, \"\").trim());\n }\n blankLineBefore = true;\n inNote = false;\n continue;\n }\n\n // New note\n if (blankLineBefore && line.startsWith(\">\")) {\n flushPendingItems(section, items);\n noteText = line.substring(1).trim();\n inNote = true;\n blankLineBefore = false;\n continue;\n }\n\n // Continue note\n if (inNote) {\n if (line.startsWith(\">\")) {\n noteText += \" \" + line.substring(1).trim();\n } else {\n noteText += \" \" + line.trim();\n }\n blankLineBefore = false;\n continue;\n }\n\n // Detecting items\n let cursor = 0;\n for (const match of line.matchAll(tokensRegex)) {\n const idx = match.index;\n /* v8 ignore else -- @preserve */\n if (idx > cursor) {\n items.push({ type: \"text\", value: line.slice(cursor, idx) });\n }\n\n const groups = match.groups!;\n\n // Ingredient items with potential in-line alternatives\n if (groups.mIngredientName || groups.sIngredientName) {\n this._parseIngredientWithAlternativeRecursive(match[0], items);\n }\n // Ingredient items part of a group of alternative ingredients\n else if (groups.gmIngredientName || groups.gsIngredientName) {\n this._parseIngredientWithGroupKey(match[0], items);\n }\n // Cookware items\n else if (groups.mCookwareName || groups.sCookwareName) {\n const name = (groups.mCookwareName || groups.sCookwareName)!;\n const modifiers = groups.cookwareModifiers;\n const quantityRaw = groups.cookwareQuantity;\n const reference = modifiers !== undefined && modifiers.includes(\"&\");\n const flags: CookwareFlag[] = [];\n if (modifiers !== undefined && modifiers.includes(\"?\")) {\n flags.push(\"optional\");\n }\n if (modifiers !== undefined && modifiers.includes(\"-\")) {\n flags.push(\"hidden\");\n }\n const quantity = quantityRaw\n ? parseQuantityInput(quantityRaw)\n : undefined;\n const newCookware: Cookware = {\n name,\n };\n if (quantity) {\n newCookware.quantity = quantity;\n }\n if (flags.length > 0) {\n newCookware.flags = flags;\n }\n\n // Add cookware in cookware list\n const idxInList = findAndUpsertCookware(\n this.cookware,\n newCookware,\n reference,\n );\n\n // Adding the item itself in the preparation\n const newItem: CookwareItem = {\n type: \"cookware\",\n index: idxInList,\n };\n if (quantity) {\n newItem.quantity = quantity;\n }\n items.push(newItem);\n }\n // Arbitrary scalable quantities\n else if (groups.arbitraryQuantity) {\n this._parseArbitraryScalable(groups, items);\n }\n // Then it's necessarily a timer which was matched\n else {\n const durationStr = groups.timerQuantity!.trim();\n const unit = (groups.timerUnit || \"\").trim();\n if (!unit) {\n throw new Error(\"Timer missing unit\");\n }\n const name = groups.timerName || undefined;\n const duration = parseQuantityInput(durationStr);\n const timerObj: Timer = {\n name,\n duration,\n unit,\n };\n items.push({ type: \"timer\", index: this.timers.push(timerObj) - 1 });\n }\n\n cursor = idx + match[0].length;\n }\n\n if (cursor < line.length) {\n items.push({ type: \"text\", value: line.slice(cursor) });\n }\n\n blankLineBefore = false;\n }\n\n // End of content reached: pushing all temporarily saved elements\n flushPendingItems(section, items);\n flushPendingNote(section, noteText ? this._parseNoteText(noteText) : []);\n if (!section.isBlank()) {\n this.sections.push(section);\n }\n\n this._populate_ingredient_quantities();\n }\n\n /**\n * Scales the recipe to a new number of servings. In practice, it calls\n * {@link Recipe.scaleBy | scaleBy} with a factor corresponding to the ratio between `newServings`\n * and the recipe's {@link Recipe.servings | servings} value.\n * @param newServings - The new number of servings.\n * @returns A new Recipe instance with the scaled ingredients.\n * @throws `Error` if the recipe does not contains an initial {@link Recipe.servings | servings} value\n */\n scaleTo(newServings: number): Recipe {\n let originalServings = this.getServings();\n\n // Default to 1 if no servings defined\n if (originalServings === undefined || originalServings === 0) {\n originalServings = 1;\n }\n\n const factor = Big(newServings).div(originalServings);\n return this.scaleBy(factor);\n }\n\n /**\n * Scales the recipe by a factor.\n * @param factor - The factor to scale the recipe by. While integers can be passed as-is, it is recommended to pass fractions as\n * [Big](https://github.com/MikeMcl/big.js/) values, e.g. `Big(num).div(den)` in order to avoid undesirable floating point operation inaccuracies.\n * @returns A new Recipe instance with the scaled ingredients.\n */\n scaleBy(factor: number | Big): Recipe {\n const newRecipe = this.clone();\n\n let originalServings = newRecipe.getServings();\n\n // Default to 1 if no servings defined\n if (originalServings === undefined || originalServings === 0) {\n originalServings = 1;\n }\n\n // Get unit system for best unit optimization (if set)\n const unitSystem = this.unitSystem;\n\n function scaleAlternativesBy(\n alternatives: IngredientAlternative[],\n factor: number | Big,\n ) {\n for (const alternative of alternatives) {\n if (alternative.quantity) {\n const scaleFactor = alternative.scalable ? Big(factor) : 1;\n // Scale the primary quantity\n if (\n alternative.quantity.type !== \"fixed\" ||\n alternative.quantity.value.type !== \"text\"\n ) {\n alternative.quantity = multiplyQuantityValue(\n alternative.quantity,\n scaleFactor,\n );\n }\n // Scale equivalents if any\n if (alternative.equivalents) {\n alternative.equivalents = alternative.equivalents.map(\n (altQuantity: QuantityWithExtendedUnit) => {\n if (\n altQuantity.quantity.type === \"fixed\" &&\n altQuantity.quantity.value.type === \"text\"\n ) {\n return altQuantity;\n } else {\n return {\n ...altQuantity,\n quantity: multiplyQuantityValue(\n altQuantity.quantity,\n scaleFactor,\n ),\n };\n }\n },\n );\n }\n\n // Apply best unit optimization (infers system from unit if unitSystem not set)\n // Apply to primary\n const optimizedPrimary = applyBestUnit(\n {\n quantity: alternative.quantity,\n unit: alternative.unit,\n },\n unitSystem,\n );\n alternative.quantity = optimizedPrimary.quantity;\n alternative.unit = optimizedPrimary.unit;\n\n // Apply to equivalents\n if (alternative.equivalents) {\n alternative.equivalents = alternative.equivalents.map((eq) =>\n applyBestUnit(eq, unitSystem),\n );\n }\n }\n }\n }\n\n // Scale IngredientItems\n for (const section of newRecipe.sections) {\n for (const step of section.content.filter(\n (item) => item.type === \"step\",\n )) {\n for (const item of step.items.filter(\n (item) => item.type === \"ingredient\",\n )) {\n scaleAlternativesBy(item.alternatives, factor);\n }\n }\n }\n\n // Scale Choices\n for (const alternatives of newRecipe.choices.ingredientGroups.values()) {\n scaleAlternativesBy(alternatives, factor);\n }\n for (const alternatives of newRecipe.choices.ingredientItems.values()) {\n scaleAlternativesBy(alternatives, factor);\n }\n\n // Scale Arbitraries\n for (const arbitrary of newRecipe.arbitraries) {\n arbitrary.quantity = multiplyQuantityValue(\n arbitrary.quantity,\n factor,\n ) as FixedNumericValue;\n }\n\n newRecipe._populate_ingredient_quantities();\n\n newRecipe.servings = Big(originalServings).times(factor).toNumber();\n\n /* v8 ignore else -- @preserve */\n if (newRecipe.metadata.servings && this.metadata.servings) {\n if (\n floatRegex.test(String(this.metadata.servings).replace(\",\", \".\").trim())\n ) {\n const servingsValue = parseFloat(\n String(this.metadata.servings).replace(\",\", \".\"),\n );\n newRecipe.metadata.servings = String(\n Big(servingsValue).times(factor).toNumber(),\n );\n }\n }\n\n /* v8 ignore else -- @preserve */\n if (newRecipe.metadata.yield && this.metadata.yield) {\n if (\n floatRegex.test(String(this.metadata.yield).replace(\",\", \".\").trim())\n ) {\n const yieldValue = parseFloat(\n String(this.metadata.yield).replace(\",\", \".\"),\n );\n newRecipe.metadata.yield = String(\n Big(yieldValue).times(factor).toNumber(),\n );\n }\n }\n\n /* v8 ignore else -- @preserve */\n if (newRecipe.metadata.serves && this.metadata.serves) {\n if (\n floatRegex.test(String(this.metadata.serves).replace(\",\", \".\").trim())\n ) {\n const servesValue = parseFloat(\n String(this.metadata.serves).replace(\",\", \".\"),\n );\n newRecipe.metadata.serves = String(\n Big(servesValue).times(factor).toNumber(),\n );\n }\n }\n\n return newRecipe;\n }\n\n /**\n * Converts all ingredient quantities in the recipe to a target unit system.\n *\n * @param system - The target unit system to convert to (metric, US, UK, JP)\n * @param method - How to handle existing quantities:\n * - \"keep\": Keep all existing equivalents (swap if needed, or add converted)\n * - \"replace\": Replace primary with target system quantity, discard equivalent used for conversion\n * - \"remove\": Only keep target system quantity, delete all equivalents\n * @returns A new Recipe instance with converted quantities\n *\n * @example\n * ```typescript\n * // Convert a recipe to metric, keeping original units as equivalents\n * const metricRecipe = recipe.convertTo(\"metric\", \"keep\");\n *\n * // Convert to US units, removing all other equivalents\n * const usRecipe = recipe.convertTo(\"US\", \"remove\");\n * ```\n */\n convertTo(\n system: SpecificUnitSystem,\n method: \"keep\" | \"replace\" | \"remove\",\n ): Recipe {\n const newRecipe = this.clone();\n\n /**\n * Helper to build new primary quantity fields from a converted quantity\n */\n function buildNewPrimary(\n convertedQty: QuantityWithExtendedUnit,\n oldPrimary: QuantityWithExtendedUnit,\n remainingEquivalents: QuantityWithExtendedUnit[],\n scalable: boolean,\n integerProtected: boolean | undefined,\n source: \"converted\" | \"swapped\",\n ): MaybeScalableQuantity {\n const newUnit: Unit | undefined =\n integerProtected && convertedQty.unit\n ? { name: convertedQty.unit.name, integerProtected: true }\n : convertedQty.unit;\n\n const newPrimary: MaybeScalableQuantity = {\n quantity: convertedQty.quantity,\n unit: newUnit,\n scalable,\n };\n\n if (method === \"remove\") {\n return newPrimary;\n } else if (method === \"replace\") {\n if (remainingEquivalents.length > 0) {\n // Keep remaining equivalents\n newPrimary.equivalents = remainingEquivalents;\n // An equivalent was converted and replaced, we still want to keep the oldPrimary\n if (source === \"converted\") newPrimary.equivalents.push(oldPrimary);\n }\n } else {\n // method === \"keep\": include old primary + remaining equivalents\n newPrimary.equivalents = [oldPrimary, ...remainingEquivalents];\n }\n\n return newPrimary;\n }\n\n /**\n * Convert a single alternative's quantity to the target system.\n */\n function convertAlternativeQuantity(\n alternative: IngredientAlternative & MaybeScalableQuantity,\n ): MaybeScalableQuantity {\n const primaryUnit = resolveUnit(alternative.unit?.name);\n const equivalents = alternative.equivalents ?? [];\n const oldPrimary: QuantityWithExtendedUnit = {\n quantity: alternative.quantity,\n unit: alternative.unit,\n };\n\n // Check if primary is already in target system\n if (\n primaryUnit.type !== \"other\" &&\n isUnitCompatibleWithSystem(primaryUnit, system)\n ) {\n // Primary is already in target system\n if (method === \"remove\") {\n return {\n quantity: alternative.quantity,\n unit: alternative.unit,\n scalable: alternative.scalable,\n };\n }\n return {\n quantity: alternative.quantity,\n unit: alternative.unit,\n scalable: alternative.scalable,\n equivalents,\n };\n }\n\n // Look for an equivalent in the target system\n const targetEquivIndex = equivalents.findIndex((eq) => {\n const eqUnit = resolveUnit(eq.unit?.name);\n return (\n eqUnit.type !== \"other\" && isUnitCompatibleWithSystem(eqUnit, system)\n );\n });\n\n if (targetEquivIndex !== -1) {\n // Found an equivalent in target system - swap with primary\n const targetEquiv = equivalents[targetEquivIndex]!;\n const remainingEquivalents = equivalents.filter(\n (_, i) => i !== targetEquivIndex,\n );\n return buildNewPrimary(\n targetEquiv,\n oldPrimary,\n remainingEquivalents,\n alternative.scalable,\n targetEquiv.unit?.integerProtected,\n \"swapped\",\n );\n }\n\n // No equivalent in target system - try to convert from primary\n const converted = convertQuantityToSystem(oldPrimary, system);\n\n if (converted && converted.unit) {\n return buildNewPrimary(\n converted,\n oldPrimary,\n equivalents,\n alternative.scalable,\n alternative.unit?.integerProtected,\n \"swapped\",\n );\n }\n\n // Primary cannot be converted - try to convert from equivalents\n for (let i = 0; i < equivalents.length; i++) {\n const equiv = equivalents[i]!;\n const convertedEquiv = convertQuantityToSystem(equiv, system);\n\n // v8 ignore else -- @preserve\n if (convertedEquiv && convertedEquiv.unit) {\n const remainingEquivalents =\n method === \"keep\"\n ? equivalents\n : equivalents.filter((_, idx) => idx !== i);\n return buildNewPrimary(\n convertedEquiv,\n oldPrimary,\n remainingEquivalents,\n alternative.scalable,\n equiv.unit?.integerProtected,\n \"converted\",\n );\n }\n }\n\n // Cannot convert - return as-is (or with cleared equivalents for \"remove\")\n // v8 ignore next -- @preserve\n if (method === \"remove\") {\n return {\n quantity: alternative.quantity,\n unit: alternative.unit,\n scalable: alternative.scalable,\n };\n } else {\n return {\n quantity: alternative.quantity,\n unit: alternative.unit,\n scalable: alternative.scalable,\n equivalents,\n };\n }\n }\n\n /**\n * Convert all alternatives in a list\n */\n function convertAlternatives(alternatives: IngredientAlternative[]) {\n for (const alternative of alternatives) {\n // v8 ignore else -- @preserve\n if (alternative.quantity) {\n const converted = convertAlternativeQuantity(\n alternative as IngredientAlternative & MaybeScalableQuantity,\n );\n alternative.quantity = converted.quantity;\n alternative.unit = converted.unit;\n (\n alternative as IngredientAlternative & MaybeScalableQuantity\n ).scalable = converted.scalable;\n alternative.equivalents = converted.equivalents;\n }\n }\n }\n\n // Convert IngredientItems in sections\n for (const section of newRecipe.sections) {\n for (const step of section.content.filter(\n (item) => item.type === \"step\",\n )) {\n for (const item of step.items.filter(\n (item) => item.type === \"ingredient\",\n )) {\n convertAlternatives(item.alternatives);\n }\n }\n }\n\n // Convert Choices\n for (const alternatives of newRecipe.choices.ingredientGroups.values()) {\n convertAlternatives(alternatives);\n }\n for (const alternatives of newRecipe.choices.ingredientItems.values()) {\n convertAlternatives(alternatives);\n }\n\n // Re-aggregate ingredient quantities\n newRecipe._populate_ingredient_quantities();\n\n // Setting the unit system in 'keep' mode will convert all equivalents to that system\n // which will lead to duplicates\n if (method !== \"keep\") Recipe.unitSystems.set(newRecipe, system);\n\n return newRecipe;\n }\n\n /**\n * Gets the number of servings for the recipe.\n * @private\n * @returns The number of servings, or undefined if not set.\n */\n private getServings(): number | undefined {\n if (this.servings) {\n return this.servings;\n }\n return undefined;\n }\n\n /**\n * Clones the recipe.\n * @returns A new Recipe instance with the same properties.\n */\n clone(): Recipe {\n const newRecipe = new Recipe();\n newRecipe.choices = deepClone(this.choices);\n Recipe.itemCounts.set(newRecipe, this.getItemCount());\n // deep copy\n newRecipe.metadata = deepClone(this.metadata);\n newRecipe.ingredients = deepClone(this.ingredients);\n newRecipe.sections = this.sections.map((section) => {\n const newSection = new Section(section.name);\n newSection.content = deepClone(section.content);\n return newSection;\n });\n newRecipe.cookware = deepClone(this.cookware);\n newRecipe.timers = deepClone(this.timers);\n newRecipe.arbitraries = deepClone(this.arbitraries);\n newRecipe.servings = this.servings;\n return newRecipe;\n }\n}\n","import { CategoryConfig } from \"./category_config\";\nimport { Recipe } from \"./recipe\";\nimport type {\n CategorizedIngredients,\n AddedRecipe,\n AddedIngredient,\n QuantityWithExtendedUnit,\n QuantityWithPlainUnit,\n MaybeNestedGroup,\n FlatOrGroup,\n AddedRecipeOptions,\n} from \"../types\";\nimport { addEquivalentsAndSimplify } from \"../quantities/alternatives\";\nimport { extendAllUnits } from \"../quantities/mutations\";\nimport { isAndGroup } from \"../utils/type_guards\";\n\n/**\n * Shopping List generator.\n *\n * ## Usage\n *\n * - Create a new ShoppingList instance with an optional category configuration (see {@link ShoppingList.\"constructor\" | constructor})\n * - Add recipes, scaling them as needed (see {@link ShoppingList.add_recipe | add_recipe()})\n * - Categorize the ingredients (see {@link ShoppingList.categorize | categorize()})\n *\n * @example\n *\n * ```typescript\n * import * as fs from \"fs\";\n * import { ShoppingList } from @tmlmt/cooklang-parser;\n *\n * const categoryConfig = fs.readFileSync(\"./myconfig.txt\", \"utf-8\")\n * const recipe1 = new Recipe(fs.readFileSync(\"./myrecipe.cook\", \"utf-8\"));\n * const shoppingList = new ShoppingList();\n * shoppingList.set_category_config(categoryConfig);\n * // Quantities are automatically calculated and ingredients categorized\n * // when adding a recipe\n * shoppingList.add_recipe(recipe1);\n * ```\n *\n * @category Classes\n */\nexport class ShoppingList {\n // TODO: backport type change\n /**\n * The ingredients in the shopping list.\n */\n ingredients: AddedIngredient[] = [];\n /**\n * The recipes in the shopping list.\n */\n recipes: AddedRecipe[] = [];\n /**\n * The category configuration for the shopping list.\n */\n category_config?: CategoryConfig;\n /**\n * The categorized ingredients in the shopping list.\n */\n categories?: CategorizedIngredients;\n\n /**\n * Creates a new ShoppingList instance\n * @param category_config_str - The category configuration to parse.\n */\n constructor(category_config_str?: string | CategoryConfig) {\n if (category_config_str) {\n this.set_category_config(category_config_str);\n }\n }\n\n private calculate_ingredients() {\n this.ingredients = [];\n\n const addIngredientQuantity = (\n name: string,\n quantityTotal:\n | QuantityWithPlainUnit\n | MaybeNestedGroup<QuantityWithPlainUnit>,\n ) => {\n const quantityTotalExtended = extendAllUnits(quantityTotal);\n const newQuantities = (\n isAndGroup(quantityTotalExtended)\n ? quantityTotalExtended.and\n : [quantityTotalExtended]\n ) as (QuantityWithExtendedUnit | FlatOrGroup<QuantityWithExtendedUnit>)[];\n const existing = this.ingredients.find((i) => i.name === name);\n\n if (existing) {\n if (!existing.quantityTotal) {\n existing.quantityTotal = quantityTotal;\n return;\n }\n try {\n const existingQuantityTotalExtended = extendAllUnits(\n existing.quantityTotal,\n );\n const existingQuantities = (\n isAndGroup(existingQuantityTotalExtended)\n ? existingQuantityTotalExtended.and\n : [existingQuantityTotalExtended]\n ) as (\n | QuantityWithExtendedUnit\n | FlatOrGroup<QuantityWithExtendedUnit>\n )[];\n existing.quantityTotal = addEquivalentsAndSimplify([\n ...existingQuantities,\n ...newQuantities,\n ]);\n return;\n } catch {\n // Incompatible\n }\n }\n\n this.ingredients.push({\n name,\n quantityTotal,\n });\n };\n\n for (const addedRecipe of this.recipes) {\n let scaledRecipe: Recipe;\n if (\"factor\" in addedRecipe) {\n const { recipe, factor } = addedRecipe;\n scaledRecipe = factor === 1 ? recipe : recipe.scaleBy(factor);\n } else {\n scaledRecipe = addedRecipe.recipe.scaleTo(addedRecipe.servings);\n }\n\n // Get computed ingredients with total quantities based on choices (or default)\n const ingredients = scaledRecipe.getIngredientQuantities({\n choices: addedRecipe.choices,\n });\n\n for (const ingredient of ingredients) {\n // Do not add hidden ingredients to the shopping list\n if (ingredient.flags && ingredient.flags.includes(\"hidden\")) {\n continue;\n }\n\n // Only add ingredients that were selected (have usedAsPrimary flag)\n // This filters out alternative ingredients that weren't chosen\n if (!ingredient.usedAsPrimary) {\n continue;\n }\n\n // Sum up quantities from the ingredient's quantity groups\n if (ingredient.quantities && ingredient.quantities.length > 0) {\n // Extract all quantities (converting to plain units for summing)\n const allQuantities: (\n | QuantityWithPlainUnit\n | MaybeNestedGroup<QuantityWithPlainUnit>\n )[] = [];\n for (const qGroup of ingredient.quantities) {\n if (\"and\" in qGroup) {\n // AndGroup - add each quantity separately\n for (const qty of qGroup.and) {\n allQuantities.push(qty);\n }\n } else {\n // Simple quantity (strip alternatives - choices already applied)\n const plainQty: QuantityWithPlainUnit = {\n quantity: qGroup.quantity,\n };\n if (qGroup.unit) plainQty.unit = qGroup.unit;\n if (qGroup.equivalents) plainQty.equivalents = qGroup.equivalents;\n allQuantities.push(plainQty);\n }\n }\n if (allQuantities.length === 1) {\n addIngredientQuantity(ingredient.name, allQuantities[0]!);\n } else {\n // allQuantities.length > 1\n // Sum up using addEquivalentsAndSimplify\n const extendedQuantities = allQuantities.map((q) =>\n extendAllUnits(q),\n );\n const totalQuantity = addEquivalentsAndSimplify(\n extendedQuantities as (\n | QuantityWithExtendedUnit\n | FlatOrGroup<QuantityWithExtendedUnit>\n )[],\n );\n // addEquivalentsAndSimplify already returns plain units\n addIngredientQuantity(ingredient.name, totalQuantity);\n }\n } else if (!this.ingredients.some((i) => i.name === ingredient.name)) {\n this.ingredients.push({ name: ingredient.name });\n }\n }\n }\n }\n\n /**\n * Adds a recipe to the shopping list, then automatically\n * recalculates the quantities and recategorize the ingredients.\n * @param recipe - The recipe to add.\n * @param options - Options for adding the recipe.\n * @throws Error if the recipe has alternatives without corresponding choices.\n */\n add_recipe(recipe: Recipe, options: AddedRecipeOptions = {}): void {\n // Validate that choices are provided for all alternatives\n const errorMessage = this.getUnresolvedAlternativesError(\n recipe,\n options.choices,\n );\n if (errorMessage) {\n throw new Error(errorMessage);\n }\n\n if (!options.scaling) {\n this.recipes.push({\n recipe,\n factor: options.scaling ?? 1,\n choices: options.choices,\n });\n } else {\n if (\"factor\" in options.scaling) {\n this.recipes.push({\n recipe,\n factor: options.scaling.factor,\n choices: options.choices,\n });\n } else {\n this.recipes.push({\n recipe,\n servings: options.scaling.servings,\n choices: options.choices,\n });\n }\n }\n this.calculate_ingredients();\n this.categorize();\n }\n\n /**\n * Checks if a recipe has unresolved alternatives (alternatives without provided choices).\n * @param recipe - The recipe to check.\n * @param choices - The choices provided for the recipe.\n * @returns An error message if there are unresolved alternatives, undefined otherwise.\n */\n private getUnresolvedAlternativesError(\n recipe: Recipe,\n choices?: import(\"../types\").RecipeChoices,\n ): string | undefined {\n const missingItems: string[] = [];\n const missingGroups: string[] = [];\n\n // Check for inline alternatives without choices\n for (const itemId of recipe.choices.ingredientItems.keys()) {\n if (!choices?.ingredientItems?.has(itemId)) {\n missingItems.push(itemId);\n }\n }\n\n // Check for grouped alternatives without choices\n for (const groupId of recipe.choices.ingredientGroups.keys()) {\n if (!choices?.ingredientGroups?.has(groupId)) {\n missingGroups.push(groupId);\n }\n }\n\n if (missingItems.length === 0 && missingGroups.length === 0) {\n return undefined;\n }\n\n const parts: string[] = [];\n if (missingItems.length > 0) {\n parts.push(\n `ingredientItems: [${missingItems.map((i) => `'${i}'`).join(\", \")}]`,\n );\n }\n if (missingGroups.length > 0) {\n parts.push(\n `ingredientGroups: [${missingGroups.map((g) => `'${g}'`).join(\", \")}]`,\n );\n }\n return `Recipe has unresolved alternatives. Missing choices for: ${parts.join(\", \")}`;\n }\n\n /**\n * Removes a recipe from the shopping list, then automatically\n * recalculates the quantities and recategorize the ingredients.s\n * @param index - The index of the recipe to remove.\n */\n remove_recipe(index: number) {\n if (index < 0 || index >= this.recipes.length) {\n throw new Error(\"Index out of bounds\");\n }\n this.recipes.splice(index, 1);\n this.calculate_ingredients();\n this.categorize();\n }\n\n /**\n * Sets the category configuration for the shopping list\n * and automatically categorize current ingredients from the list.\n * @param config - The category configuration to parse.\n */\n set_category_config(config: string | CategoryConfig) {\n if (typeof config === \"string\")\n this.category_config = new CategoryConfig(config);\n else if (config instanceof CategoryConfig) this.category_config = config;\n else throw new Error(\"Invalid category configuration\");\n this.categorize();\n }\n\n /**\n * Categorizes the ingredients in the shopping list\n * Will use the category config if any, otherwise all ingredients will be placed in the \"other\" category\n */\n categorize() {\n if (!this.category_config) {\n this.categories = { other: this.ingredients };\n return;\n }\n\n const categories: CategorizedIngredients = { other: [] };\n for (const category of this.category_config.categories) {\n categories[category.name] = [];\n }\n\n for (const ingredient of this.ingredients) {\n let found = false;\n for (const category of this.category_config.categories) {\n for (const categoryIngredient of category.ingredients) {\n if (categoryIngredient.aliases.includes(ingredient.name)) {\n categories[category.name]!.push(ingredient);\n found = true;\n break;\n }\n }\n if (found) {\n break;\n }\n }\n if (!found) {\n categories.other!.push(ingredient);\n }\n }\n\n this.categories = categories;\n }\n}\n","import type {\n ProductOption,\n ProductSelection,\n AddedIngredient,\n CartContent,\n CartMatch,\n CartMisMatch,\n FixedNumericValue,\n Range,\n ProductOptionNormalized,\n ProductSizeNormalized,\n NoProductMatchErrorCode,\n FlatOrGroup,\n MaybeNestedGroup,\n QuantityWithUnitDef,\n} from \"../types\";\nimport { ProductCatalog } from \"./product_catalog\";\nimport { ShoppingList } from \"./shopping_list\";\nimport {\n NoProductCatalogForCartError,\n NoShoppingListForCartError,\n NoProductMatchError,\n} from \"../errors\";\nimport { resolveUnit } from \"../units/definitions\";\nimport { normalizeAllUnits } from \"../quantities/mutations\";\nimport { getNumericValue, multiplyQuantityValue } from \"../quantities/numeric\";\nimport { isAndGroup, isOrGroup } from \"../utils/type_guards\";\nimport { areUnitsGroupable } from \"../units/compatibility\";\nimport { solve, type Model } from \"yalps\";\n\n/**\n * Options for the {@link ShoppingCart} constructor\n * @category Types\n */\nexport interface ShoppingCartOptions {\n /**\n * A product catalog to connect to the cart\n */\n catalog?: ProductCatalog;\n /**\n * A shopping list to connect to the cart\n */\n list?: ShoppingList;\n}\n\n/**\n * Key information about the {@link ShoppingCart}\n * @category Types\n */\nexport interface ShoppingCartSummary {\n /**\n * The total price of the cart\n */\n totalPrice: number;\n /**\n * The total number of items in the cart\n */\n totalItems: number;\n}\n\n/**\n * Shopping Cart Manager: a tool to find the best combination of products to buy (defined in a {@link ProductCatalog}) to satisfy a {@link ShoppingList}.\n *\n * @example\n * ```ts\n * const shoppingList = new ShoppingList();\n * const recipe = new Recipe(\"@flour{600%g}\");\n * shoppingList.add_recipe(recipe);\n *\n * const catalog = new ProductCatalog();\n * catalog.products = [\n * {\n * id: \"flour-1kg\",\n * productName: \"Flour (1kg)\",\n * ingredientName: \"flour\",\n * price: 10,\n * size: { type: \"fixed\", value: { type: \"decimal\", value: 1000 } },\n * unit: \"g\",\n * },\n * {\n * id: \"flour-500g\",\n * productName: \"Flour (500g)\",\n * ingredientName: \"flour\",\n * price: 6,\n * size: { type: \"fixed\", value: { type: \"decimal\", value: 500 } },\n * unit: \"g\",\n * },\n * ];\n *\n * const shoppingCart = new ShoppingCart({list: shoppingList, catalog}))\n * shoppingCart.buildCart();\n * ```\n *\n * @category Classes\n */\nexport class ShoppingCart {\n /**\n * The product catalog to use for matching products\n */\n productCatalog?: ProductCatalog;\n /**\n * The shopping list to build the cart from\n */\n shoppingList?: ShoppingList;\n /**\n * The content of the cart\n */\n cart: CartContent = [];\n /**\n * The ingredients that were successfully matched with products\n */\n match: CartMatch = [];\n /**\n * The ingredients that could not be matched with products\n */\n misMatch: CartMisMatch = [];\n /**\n * Key information about the shopping cart\n */\n summary: ShoppingCartSummary;\n\n /**\n * Creates a new ShoppingCart instance\n * @param options - {@link ShoppingCartOptions | Options} for the constructor\n */\n constructor(options?: ShoppingCartOptions) {\n if (options?.catalog) this.productCatalog = options.catalog;\n if (options?.list) this.shoppingList = options.list;\n this.summary = { totalPrice: 0, totalItems: 0 };\n }\n\n /**\n * Sets the product catalog to use for matching products\n * To use if a catalog was not provided at the creation of the instance\n * @param catalog - The {@link ProductCatalog} to set\n */\n setProductCatalog(catalog: ProductCatalog) {\n this.productCatalog = catalog;\n }\n\n // TODO: harmonize recipe name to use underscores\n /**\n * Sets the shopping list to build the cart from.\n * To use if a shopping list was not provided at the creation of the instance\n * @param list - The {@link ShoppingList} to set\n */\n setShoppingList(list: ShoppingList) {\n this.shoppingList = list;\n }\n\n /**\n * Builds the cart from the shopping list and product catalog\n * @remarks\n * - If a combination of product(s) is successfully found for a given ingredient, the latter will be listed in the {@link ShoppingCart.match | match} array\n * in addition to that combination being added to the {@link ShoppingCart.cart | cart}.\n * - Otherwise, the latter will be listed in the {@link ShoppingCart.misMatch | misMatch} array. Possible causes can be:\n * - No product is listed in the catalog for that ingredient\n * - The ingredient has no quantity, a text quantity\n * - The ingredient's quantity unit is incompatible with the units of the candidate products listed in the catalog\n * @throws {@link NoProductCatalogForCartError} if no product catalog is set\n * @throws {@link NoShoppingListForCartError} if no shopping list is set\n * @returns `true` if all ingredients in the shopping list have been matched to products in the catalog, or `false` otherwise\n */\n buildCart(): boolean {\n this.resetCart();\n\n if (this.productCatalog === undefined) {\n throw new NoProductCatalogForCartError();\n } else if (this.shoppingList === undefined) {\n throw new NoShoppingListForCartError();\n }\n\n for (const ingredient of this.shoppingList.ingredients) {\n const productOptions = this.getProductOptions(ingredient);\n try {\n const optimumMatch = this.getOptimumMatch(ingredient, productOptions);\n this.cart.push(...optimumMatch);\n this.match.push({ ingredient, selection: optimumMatch });\n } catch (error) {\n /* v8 ignore else -- @preserve */\n if (error instanceof NoProductMatchError) {\n this.misMatch.push({ ingredient, reason: error.code });\n }\n }\n }\n\n this.summarize();\n\n return this.misMatch.length > 0;\n }\n\n /**\n * Gets the product options for a given ingredient\n * @param ingredient - The ingredient to get the product options for\n * @returns An array of {@link ProductOption}\n */\n private getProductOptions(ingredient: AddedIngredient): ProductOption[] {\n // this function is only called in buildCart() which starts by checking that a product catalog is present\n return this.productCatalog!.products.filter(\n (product) =>\n product.ingredientName === ingredient.name ||\n product.ingredientAliases?.includes(ingredient.name),\n );\n }\n\n /**\n * Gets the optimum match for a given ingredient and product option\n * @param ingredient - The ingredient to match\n * @param options - The product options to choose from\n * @returns An array of {@link ProductSelection}\n * @throws {@link NoProductMatchError} if no match can be found\n */\n private getOptimumMatch(\n ingredient: AddedIngredient,\n options: ProductOption[],\n ): ProductSelection[] {\n // If there's no product option, return an empty match\n if (options.length === 0)\n throw new NoProductMatchError(ingredient.name, \"noProduct\");\n // If the ingredient has no quantity, we can't match any product\n if (!ingredient.quantityTotal)\n throw new NoProductMatchError(ingredient.name, \"noQuantity\");\n\n // Normalize options units and scale size to base\n const normalizedOptions: ProductOptionNormalized[] = options.map(\n (option) => ({\n ...option,\n sizes: option.sizes.map((s): ProductSizeNormalized => {\n const resolvedUnit = resolveUnit(s.unit);\n return {\n size:\n resolvedUnit && \"toBase\" in resolvedUnit\n ? (multiplyQuantityValue(\n s.size,\n resolvedUnit.toBase,\n ) as FixedNumericValue)\n : s.size,\n unit: resolvedUnit,\n };\n }),\n }),\n );\n const normalizedQuantityTotal = normalizeAllUnits(ingredient.quantityTotal);\n\n function getOptimumMatchForQuantityParts(\n normalizedQuantities:\n | QuantityWithUnitDef\n | MaybeNestedGroup<QuantityWithUnitDef>,\n normalizedOptions: ProductOptionNormalized[],\n selection: ProductSelection[] = [],\n ): ProductSelection[] {\n if (isAndGroup(normalizedQuantities)) {\n for (const q of normalizedQuantities.and) {\n const result = getOptimumMatchForQuantityParts(\n q,\n normalizedOptions,\n selection,\n );\n selection.push(...result);\n }\n } else {\n const alternativeUnitsOfQuantity = isOrGroup(normalizedQuantities)\n ? (normalizedQuantities as FlatOrGroup<QuantityWithUnitDef>).or\n : [normalizedQuantities];\n const solutions: ProductSelection[][] = [];\n const errors = new Set<NoProductMatchErrorCode>();\n for (const alternative of alternativeUnitsOfQuantity) {\n // At this stage, we're treating individual Quantities we should try to match\n if (\n alternative.quantity.type === \"fixed\" &&\n alternative.quantity.value.type === \"text\"\n ) {\n errors.add(\"textValue\");\n continue;\n }\n // At this stage, we know there is a numerical quantity\n // So we scale it to base in order to calculate the correct quantity\n const scaledQuantity = multiplyQuantityValue(\n alternative.quantity,\n \"toBase\" in alternative.unit ? alternative.unit.toBase : 1,\n ) as FixedNumericValue | Range;\n alternative.quantity = scaledQuantity;\n // Are there compatible product options for that specific unit alternative?\n // A product is compatible if ANY of its sizes has a compatible unit\n const matchOptions = normalizedOptions.filter((option) =>\n option.sizes.some((s) =>\n areUnitsGroupable(alternative.unit, s.unit),\n ),\n );\n if (matchOptions.length > 0) {\n // Helper to find the compatible size for a product option\n const findCompatibleSize = (\n option: ProductOptionNormalized,\n ): ProductSizeNormalized =>\n option.sizes.find((s) =>\n areUnitsGroupable(alternative.unit, s.unit),\n )!;\n\n // Simple minimization exercise if only one product option\n if (matchOptions.length == 1) {\n const matchedOption = matchOptions[0]!;\n const compatibleSize = findCompatibleSize(matchedOption);\n const product = options.find(\n (opt) => opt.id === matchedOption.id,\n )!;\n // FixedValue\n const targetQuantity =\n scaledQuantity.type === \"fixed\"\n ? scaledQuantity.value\n : scaledQuantity.min;\n const resQuantity = Math.ceil(\n getNumericValue(targetQuantity) /\n getNumericValue(compatibleSize.size.value),\n );\n solutions.push([\n {\n product,\n quantity: resQuantity,\n totalPrice: resQuantity * matchedOption.price,\n },\n ]);\n continue;\n }\n\n // More complex problem if there are several options\n const model: Model = {\n direction: \"minimize\",\n objective: \"price\",\n integers: true,\n constraints: {\n size: {\n min:\n scaledQuantity.type === \"fixed\"\n ? getNumericValue(scaledQuantity.value)\n : getNumericValue(scaledQuantity.min),\n },\n },\n variables: matchOptions.reduce(\n (acc, option) => {\n const compatibleSize = findCompatibleSize(option);\n acc[option.id] = {\n price: option.price,\n size: getNumericValue(compatibleSize.size.value),\n };\n return acc;\n },\n {} as Record<string, { price: number; size: number }>,\n ),\n };\n\n const solution = solve(model);\n solutions.push(\n solution.variables.map((variable) => {\n const resProductSelection = {\n product: options.find((option) => option.id === variable[0])!,\n quantity: variable[1],\n };\n return {\n ...resProductSelection,\n totalPrice:\n resProductSelection.quantity *\n resProductSelection.product.price,\n };\n }),\n );\n } else {\n errors.add(\"incompatibleUnits\");\n }\n }\n // All alternatives were checked\n if (solutions.length === 0) {\n throw new NoProductMatchError(\n ingredient.name,\n errors.size === 1\n ? (errors.values().next().value as NoProductMatchErrorCode)\n : \"textValue_incompatibleUnits\",\n );\n } else {\n // We return the cheapest solution among those found\n return solutions.sort(\n (a, b) =>\n a.reduce((acc, item) => acc + item.totalPrice, 0) -\n b.reduce((acc, item) => acc + item.totalPrice, 0),\n )[0]!;\n }\n }\n return selection;\n }\n\n return getOptimumMatchForQuantityParts(\n normalizedQuantityTotal,\n normalizedOptions,\n );\n }\n\n /**\n * Reset the cart's properties\n */\n private resetCart() {\n this.cart = [];\n this.match = [];\n this.misMatch = [];\n this.summary = { totalPrice: 0, totalItems: 0 };\n }\n\n /**\n * Calculate the cart's key info and store it in the cart's {@link ShoppingCart.summary | summary} property.\n * This function is automatically invoked by {@link ShoppingCart.buildCart | buildCart() } method.\n * @returns the total price and number of items in the cart\n */\n summarize(): ShoppingCartSummary {\n this.summary.totalPrice = this.cart.reduce(\n (acc, item) => acc + item.totalPrice,\n 0,\n );\n this.summary.totalItems = this.cart.length;\n return this.summary;\n }\n}\n","import type {\n IngredientItem,\n RecipeChoices,\n FixedValue,\n Range,\n TextValue,\n DecimalValue,\n FractionValue,\n Unit,\n QuantityWithExtendedUnit,\n MaybeScalableQuantity,\n} from \"../types\";\nimport { Recipe } from \"../classes/recipe\";\n\n// ============================================================================\n// Quantity Formatting Helpers\n// ============================================================================\n\n/**\n * Map of common fractions to their Unicode vulgar fraction characters.\n */\nconst VULGAR_FRACTIONS: Record<string, string> = {\n \"1/2\": \"½\",\n \"1/3\": \"⅓\",\n \"2/3\": \"⅔\",\n \"1/4\": \"¼\",\n \"3/4\": \"¾\",\n \"1/8\": \"⅛\",\n \"3/8\": \"⅜\",\n \"5/8\": \"⅝\",\n \"7/8\": \"⅞\",\n};\n\n/**\n * Render a fraction using Unicode vulgar fraction characters when available.\n * Handles improper fractions by extracting the whole part (e.g., 5/4 → \"1¼\").\n *\n * @param num - The numerator\n * @param den - The denominator\n * @returns The fraction as a string, using vulgar characters if available\n * @category Helpers\n *\n * @example\n * ```typescript\n * renderFractionAsVulgar(1, 2); // \"½\"\n * renderFractionAsVulgar(3, 4); // \"¾\"\n * renderFractionAsVulgar(5, 4); // \"1¼\"\n * renderFractionAsVulgar(7, 3); // \"2⅓\"\n * renderFractionAsVulgar(2, 5); // \"2/5\" (no vulgar character available)\n * ```\n */\nexport function renderFractionAsVulgar(num: number, den: number): string {\n // Handle improper fractions (num >= den)\n const wholePart = Math.floor(num / den);\n const remainder = num % den;\n\n if (remainder === 0) {\n // Exact integer\n return String(wholePart);\n }\n\n const fractionKey = `${remainder}/${den}`;\n const vulgar = VULGAR_FRACTIONS[fractionKey];\n\n if (wholePart > 0) {\n // Mixed fraction: whole part + fractional part\n return vulgar\n ? `${wholePart}${vulgar}`\n : `${wholePart} ${remainder}/${den}`;\n }\n\n // Proper fraction only\n return vulgar ?? `${num}/${den}`;\n}\n\n/**\n * Format a numeric value (decimal or fraction) to a string.\n *\n * @param value - The decimal or fraction value to format\n * @param useVulgar - Whether to use Unicode vulgar fraction characters (default: false)\n * @returns The formatted string representation\n * @category Helpers\n *\n * @example\n * ```typescript\n * formatNumericValue({ type: \"decimal\", decimal: 1.5 }); // \"1.5\"\n * formatNumericValue({ type: \"fraction\", num: 1, den: 2 }); // \"1/2\"\n * formatNumericValue({ type: \"fraction\", num: 1, den: 2 }, true); // \"½\"\n * formatNumericValue({ type: \"fraction\", num: 5, den: 4 }, true); // \"1¼\"\n * ```\n */\nexport function formatNumericValue(\n value: DecimalValue | FractionValue,\n useVulgar: boolean = true,\n): string {\n if (value.type === \"decimal\") {\n return String(value.decimal);\n }\n if (useVulgar) {\n return renderFractionAsVulgar(value.num, value.den);\n }\n return `${value.num}/${value.den}`;\n}\n\n/**\n * Format a single value (text, decimal, or fraction) to a string.\n *\n * @param value - The value to format\n * @returns The formatted string representation\n * @category Helpers\n *\n * @example\n * ```typescript\n * formatSingleValue({ type: \"text\", text: \"a pinch\" }); // \"a pinch\"\n * formatSingleValue({ type: \"decimal\", decimal: 2 }); // \"2\"\n * formatSingleValue({ type: \"fraction\", num: 3, den: 4 }); // \"3/4\"\n * ```\n */\nexport function formatSingleValue(\n value: TextValue | DecimalValue | FractionValue,\n): string {\n if (value.type === \"text\") {\n return value.text;\n }\n return formatNumericValue(value);\n}\n\n/**\n * Format a quantity (fixed value or range) to a string.\n *\n * @param quantity - The quantity to format\n * @returns The formatted string representation\n * @category Helpers\n *\n * @example\n * ```typescript\n * formatQuantity({ type: \"fixed\", value: { type: \"decimal\", decimal: 100 } }); // \"100\"\n * formatQuantity({ type: \"range\", min: { type: \"decimal\", decimal: 1 }, max: { type: \"decimal\", decimal: 2 } }); // \"1-2\"\n * ```\n */\nexport function formatQuantity(quantity: FixedValue | Range): string {\n if (quantity.type === \"fixed\") {\n return formatSingleValue(quantity.value);\n }\n // Range\n const minStr = formatNumericValue(quantity.min);\n const maxStr = formatNumericValue(quantity.max);\n return `${minStr}-${maxStr}`;\n}\n\n/**\n * Format a unit to a string. Handles both plain string units and Unit objects.\n *\n * @param unit - The unit to format (string, Unit object, or undefined)\n * @returns The formatted unit string, or empty string if undefined\n * @category Helpers\n *\n * @example\n * ```typescript\n * formatUnit(\"g\"); // \"g\"\n * formatUnit({ name: \"grams\" }); // \"grams\"\n * formatUnit(undefined); // \"\"\n * ```\n */\nexport function formatUnit(unit: string | Unit | undefined): string {\n if (!unit) return \"\";\n if (typeof unit === \"string\") return unit;\n return unit.name;\n}\n\n/**\n * Format a quantity with its unit to a string.\n *\n * @param quantity - The quantity to format\n * @param unit - The unit to append (string, Unit object, or undefined)\n * @returns The formatted string with quantity and unit\n * @category Helpers\n *\n * @example\n * ```typescript\n * formatQuantityWithUnit({ type: \"fixed\", value: { type: \"decimal\", decimal: 100 } }, \"g\"); // \"100 g\"\n * formatQuantityWithUnit({ type: \"fixed\", value: { type: \"decimal\", decimal: 2 } }, undefined); // \"2\"\n * ```\n */\nexport function formatQuantityWithUnit(\n quantity: FixedValue | Range | undefined,\n unit: string | Unit | undefined,\n): string {\n if (!quantity) return \"\";\n const qty = formatQuantity(quantity);\n const unitStr = formatUnit(unit);\n return unitStr ? `${qty} ${unitStr}` : qty;\n}\n\n/**\n * Format a QuantityWithExtendedUnit to a string.\n *\n * @param item - The quantity with extended unit to format\n * @returns The formatted string\n * @category Helpers\n */\nexport function formatExtendedQuantity(item: QuantityWithExtendedUnit): string {\n return formatQuantityWithUnit(item.quantity, item.unit);\n}\n\n/**\n * Format an IngredientItemQuantity with all its equivalents to a string.\n *\n * @param itemQuantity - The ingredient item quantity to format\n * @param separator - The separator between primary and equivalent quantities (default: \" | \")\n * @returns The formatted string with all quantities\n * @category Helpers\n *\n * @example\n * ```typescript\n * // For an ingredient like @flour{100%g|3.5%oz}\n * formatItemQuantity(itemQuantity); // \"100 g | 3.5 oz\"\n * formatItemQuantity(itemQuantity, \" / \"); // \"100 g / 3.5 oz\"\n * ```\n */\nexport function formatItemQuantity(\n itemQuantity: MaybeScalableQuantity,\n separator: string = \" | \",\n): string {\n const parts: string[] = [];\n\n // Primary quantity\n parts.push(formatExtendedQuantity(itemQuantity));\n\n // Equivalents\n if (itemQuantity.equivalents) {\n for (const eq of itemQuantity.equivalents) {\n parts.push(formatExtendedQuantity(eq));\n }\n }\n\n return parts.join(separator);\n}\n\n// ============================================================================\n// Ingredient Item Helpers\n// ============================================================================\n\n/**\n * Check if an ingredient item is a grouped alternative (vs inline alternative).\n *\n * Grouped alternatives are ingredients that share a group key (e.g., `@|milk|...`)\n * and are distributed across multiple tokens in the recipe.\n *\n * @param item - The ingredient item to check\n * @returns true if this is a grouped alternative\n * @category Helpers\n *\n * @example\n * ```typescript\n * for (const item of step.items) {\n * if (item.type === 'ingredient') {\n * if (isGroupedItem(item)) {\n * // Handle grouped alternative (e.g., show with strikethrough if not selected)\n * } else {\n * // Handle inline alternative (e.g., hide if not selected)\n * }\n * }\n * }\n * ```\n */\nexport function isGroupedItem(item: IngredientItem): boolean {\n return item.group !== undefined;\n}\n\n// ============================================================================\n// Alternative Selection Helpers\n// ============================================================================\n\n/**\n * Determines if a specific alternative in an IngredientItem is selected\n * based on the applied choices.\n *\n * Use this in renderers to determine how an ingredient alternative should be displayed.\n *\n * @param recipe - The Recipe instance containing choices\n * @param choices - The choices that have been made\n * @param item - The IngredientItem to check\n * @param alternativeIndex - The index within item.alternatives to check (for inline alternatives only)\n * @returns true if this alternative is the selected one\n * @category Helpers\n *\n * @example\n * ```typescript\n * const recipe = new Recipe(cooklangText);\n * for (const item of step.items) {\n * if (item.type === 'ingredient') {\n * item.alternatives.forEach((alt, idx) => {\n * const isSelected = isAlternativeSelected(item, idx, recipe, choices);\n * // Render differently based on isSelected\n * });\n * }\n * }\n * ```\n */\nexport function isAlternativeSelected(\n recipe: Recipe,\n choices: RecipeChoices,\n item: IngredientItem,\n alternativeIndex?: number,\n): boolean {\n // Grouped alternatives: check ingredientGroups map\n if (item.group) {\n // Get the selected index in the group\n const selectedIndex = choices?.ingredientGroups?.get(item.group);\n // Get the alternatives array for this group\n const groupAlternatives = recipe.choices.ingredientGroups.get(item.group);\n if (\n groupAlternatives &&\n selectedIndex !== undefined &&\n selectedIndex < groupAlternatives.length\n ) {\n // Check if the selected alternative's itemId matches this item's id\n const selectedItemId = groupAlternatives[selectedIndex]?.itemId;\n return selectedItemId === item.id;\n }\n return false;\n }\n\n // Inline alternatives: check ingredientItems map\n const selectedIndex = choices?.ingredientItems?.get(item.id);\n return alternativeIndex === selectedIndex;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACiCO,IAAM,iBAAN,MAAqB;AAAA;AAAA;AAAA;AAAA;AAAA,EAU1B,YAAY,QAAiB;AAN7B;AAAA;AAAA;AAAA,sCAAyB,CAAC;AAOxB,QAAI,QAAQ;AACV,WAAK,MAAM,MAAM;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAgB;AACpB,QAAI,kBAAmC;AACvC,UAAM,gBAAgB,oBAAI,IAAY;AACtC,UAAM,kBAAkB,oBAAI,IAAY;AAExC,eAAW,QAAQ,OAAO,MAAM,IAAI,GAAG;AACrC,YAAM,cAAc,KAAK,KAAK;AAE9B,UAAI,YAAY,WAAW,GAAG;AAC5B;AAAA,MACF;AAEA,UAAI,YAAY,WAAW,GAAG,KAAK,YAAY,SAAS,GAAG,GAAG;AAC5D,cAAM,eAAe,YAClB,UAAU,GAAG,YAAY,SAAS,CAAC,EACnC,KAAK;AAER,YAAI,cAAc,IAAI,YAAY,GAAG;AACnC,gBAAM,IAAI,MAAM,6BAA6B,YAAY,EAAE;AAAA,QAC7D;AACA,sBAAc,IAAI,YAAY;AAE9B,0BAAkB,EAAE,MAAM,cAAc,aAAa,CAAC,EAAE;AACxD,aAAK,WAAW,KAAK,eAAe;AAAA,MACtC,OAAO;AACL,YAAI,oBAAoB,MAAM;AAC5B,gBAAM,IAAI;AAAA,YACR,wCAAwC,WAAW;AAAA,UACrD;AAAA,QACF;AAEA,cAAM,UAAU,YAAY,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAC1D,mBAAW,SAAS,SAAS;AAC3B,cAAI,gBAAgB,IAAI,KAAK,GAAG;AAC9B,kBAAM,IAAI,MAAM,qCAAqC,KAAK,EAAE;AAAA,UAC9D;AACA,0BAAgB,IAAI,KAAK;AAAA,QAC3B;AAEA,cAAM,aAAiC;AAAA,UACrC,MAAM,QAAQ,CAAC;AAAA;AAAA,UACf;AAAA,QACF;AACA,wBAAgB,YAAY,KAAK,UAAU;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;AACF;;;ACrGA,uBAAiB;;;ACEjB,IAAMA,IAAc,oBAAIC;AAAxB,IAEMC,IAAQ,EACZC,QAAQ,KACRC,eAAe,KACfC,WAAW,KACXC,SAAS,KACTC,SAAS,KACTC,QAAQ,IAAA;AARV,IAWMC,IAASC,OAAOC,OAAO,EAC3BC,OAAO,OACPC,iBAAiB,OACjBC,iBAAiB,OACjBC,QAAQ,UACRC,cAAc,aACdC,cAAc,IAAA,CAAA;AAjBhB,IAsBMC,IAAcR,OAAOC,OAAO,EAChCQ,YAAY,KACZC,WAAW,KACXC,UAAU,IAAA,CAAA;AAoBZ,IAAMC,IAAN,MAAMA;EAIJ,cAAAC;AACEC,SAAKC,QAAQ,CAAA,GACbD,KAAKE,QAAQ,oBAAIC;EAAAA;EAGnB,QAAAf;AACE,WAAOY,KAAKI,IAAI,KAAA;EAAA;EAGlB,UAAAC;AACE,WAAOL,KAAKI,IAAI,kBAAA;EAAA;EAGlB,OAAAE;AACE,WAAON,KAAKI,IAAI,KAAA;EAAA;EAGlB,aAAAG;AACE,WAAOP,KAAKI,IAAI,KAAA;EAAA;EAGlB,gBAAAI;AACE,WAAOR,KAAKI,IAAI,KAAA;EAAA;EAGlB,QAAQK,IAAAA;AACN,WAAOT,KAAKI,KAoOhB,SAAuBK,IAAAA;AAChBjC,QAAYkC,IAAID,EAAAA,KACnBjC,EAAYmC,IAAIF,IAAMA,GAAKG,QAAQ,uBAAuB,MAAA,CAAA;AAE5D,aAAOpC,EAAYqC,IAAIJ,EAAAA;IACzB,GAzOkCA,EAAAA,CAAAA;EAAAA;EAGhC,KAAAK;AACE,WAAOd,KAAKI,IAAI,GAAA;EAAA;EAGlB,MAAMW,IAAAA;AACJ,UAAMC,KAAQ/B,EAAO8B,EAAAA;AACrB,QAAA,CAAKC,GAAO,OAAM,IAAIC,MAAM,kBAAkBF,EAAAA,EAAAA;AAC9C,WAAOf,KAAKI,IAAI,IAAIY,EAAAA,GAAAA;EAAAA;EAGtB,SAASD,IAAAA;AACP,UAAMC,KAAQ/B,EAAO8B,EAAAA;AACrB,QAAA,CAAKC,GAAO,OAAM,IAAIC,MAAM,kBAAkBF,EAAAA,EAAAA;AAC9C,WAAOf,KAAKI,IAAI,KAAKY,EAAAA,GAAAA;EAAAA;EAGvB,MAAME,IAAAA;AACJ,WAAOlB,KAAKI,IAAI,IAAIc,EAAAA,GAAAA;EAAAA;EAGtB,SAASA,IAAAA;AACP,WAAOlB,KAAKI,IAAI,KAAKc,EAAAA,GAAAA;EAAAA;EAGvB,OAAAC;AACE,UAAMC,KAAWpB,KAAKC,MAAMoB,IAAAA;AAC5B,QAAA,CAAKD,GAAU,OAAM,IAAIH,MAAM,4BAAA;AAC/B,WAAOjB,KAAKI,IAAI,GAAGgB,EAAAA,GAAAA;EAAAA;EAGrB,SAAA7B;AACE,WAAOS,KAAKI,IAAI,UAAA;EAAA;EAGlB,eAAAX;AACE,WAAOO,KAAKI,IAAI,GAAA;EAAA;EAGlB,UAAAkB;AACE,WAAOtB,KAAKI,IAAI,kBAAA;EAAA;EAGlB,kBAAkBmB,IAAAA;AAChB,WAAOvB,KAAKI,IAAI,MAAMmB,EAAAA,GAAAA;EAAAA;EAGxB,kBAAkBA,IAAAA;AAChB,WAAOvB,KAAKI,IAAI,MAAMmB,EAAAA,GAAAA;EAAAA;EAGxB,mBAAmBA,IAAAA;AACjB,WAAOvB,KAAKI,IAAI,OAAOmB,EAAAA,GAAAA;EAAAA;EAGzB,mBAAmBA,IAAAA;AACjB,WAAOvB,KAAKI,IAAI,OAAOmB,EAAAA,GAAAA;EAAAA;EAGzB,sBAAAC;AACE,WAAOxB,KAAKI,IAAI,kBAAA;EAAA;EAGlB,WAAAqB;AACE,WAAOzB,KAAKI,IAAI,WAAA;EAAA;EAGlB,YAAAsB;AACE,WAAO1B,KAAKI,IAAI,gBAAA;EAAA;EAGlB,WAAAP;AACE,WAAOG,KAAKI,IAAIV,EAAYG,QAAAA;EAAAA;EAG9B,QAAQ8B,IAAAA;AACN,WAAO3B,KAAKI,IAAI,IAAIuB,EAAAA,GAAAA;EAAAA;EAGtB,QAAQA,IAAAA;AACN,WAAO3B,KAAKI,IAAI,IAAIuB,EAAAA,IAAAA;EAAAA;EAGtB,OAAOA,IAAAA;AACL,WAAO3B,KAAKI,IAAI,MAAMuB,EAAAA,GAAAA;EAAAA;EAGxB,QAAQC,IAAaC,IAAAA;AACnB,WAAO7B,KAAKI,IAAI,IAAIwB,EAAAA,IAAOC,EAAAA,GAAAA;EAAAA;EAG7B,YAAAjC;AACE,WAAOI,KAAKI,IAAIV,EAAYE,SAAAA;EAAAA;EAG9B,aAAAD;AACE,WAAOK,KAAKI,IAAIV,EAAYC,UAAAA;EAAAA;EAG9B,gBAAgBoB,IAAAA;AACd,WAAOf,KAAKI,IAAI,MAAMW,EAAAA,GAAAA;EAAAA;EAGxB,aAAAe;AACE,WAAO9B,KAAKI,IAAI,KAAA;EAAA;EAGlB,oBAAA2B;AACE,WAAO/B,KAAKI,IAAI,GAAA;EAAA;EAGlB,eAAA4B;AACE,WAAOhC,KAAKI,IAAI,KAAA;EAAA;EAGlB,kBAAA6B;AACE,WAAOjC,KAAKI,IAAI,KAAA;EAAA;EAGlB,WAAA8B;AACE,WAAOlC,KAAKI,IAAI,GAAA;EAAA;EAGlB,cAAA+B;AACE,WAAOnC,KAAKI,IAAI,GAAA;EAAA;EAGlB,YAAAgC;AACE,WAAOpC,KAAKI,IAAI,GAAA;EAAA;EAGlB,SAAAiC;AAEE,WADArC,KAAKE,MAAME,IAAI1B,EAAMC,MAAAA,GACdqB;EAAAA;EAGT,eAAAsC;AAEE,WADAtC,KAAKE,MAAME,IAAI1B,EAAME,aAAAA,GACdoB;EAAAA;EAGT,YAAAuC;AAEE,WADAvC,KAAKE,MAAME,IAAI1B,EAAMG,SAAAA,GACdmB;EAAAA;EAGT,SAAAwC;AAEE,WADAxC,KAAKE,MAAME,IAAI1B,EAAMI,OAAAA,GACdkB;EAAAA;EAGT,SAAAyC;AAEE,WADAzC,KAAKE,MAAME,IAAI1B,EAAMM,MAAAA,GACdgB;EAAAA;EAGT,YAAY0C,IAAAA;AACV1C,SAAKE,MAAME,IAAI1B,EAAMK,OAAAA;AACrB,UAAM4D,KAAgB,oBAAIxC,IAAI,CAAC,KAAK,KAAK,KAAK,KAAK,GAAA,CAAA;AAEnD,QAAA,WAAIuC,MAAAA,CAA0BC,GAAcjC,IAAIgC,EAAAA,EAC9C,OAAM,IAAIzB,MAAM,mCAAmCyB,EAAAA,EAAAA;AAGrD,WAAO1C,KAAKI,IAAI,QAAQsC,QAAAA,KAAAA,KAAW,EAAA,GAAA;EAAA;EAGrC,eAAAE;AAEE,WADA5C,KAAKE,MAAME,IAAI1B,EAAMK,OAAAA,GACdiB,KAAKI,IAAI,QAAA;EAAA;EAGlB,qBAAAyC;AAEE,WADA7C,KAAKE,MAAME,IAAI1B,EAAMK,OAAAA,GACdiB,KAAKI,IAAI,QAAA;EAAA;EAGlB,gBAAA0C;AAEE,WADA9C,KAAKE,MAAME,IAAI1B,EAAMK,OAAAA,GACdiB,KAAKI,IAAI,QAAA;EAAA;EAGlB,OAAO2C,IAAAA;AACL,QAA0B,MAAtB/C,KAAKC,MAAM+C,OACb,OAAM,IAAI/B,MAAM,sBAAA;AAGlB,UAAMG,KAAWpB,KAAKC,MAAMoB,IAAAA;AAE5B,WADArB,KAAKC,MAAMgD,KAAK,IAAI7B,EAAAA,KAAa2B,EAAAA,GAAAA,GAC1B/C;EAAAA;EAGT,YAAAkD;AACE,WAAOlD,KAAKI,IAAI,0CAAA;EAAA;EAGlB,WAAA+C;AACE,WAAOnD,KAAKI,IAAI,WAAA;EAAA;EAGlB,MAAAgD;AACE,WAAOpD,KAAKI,IAAI,WAAA;EAAA;EAGlB,MAAAiD;AACE,WAAOrD,KAAKI,IAAI,eAAA;EAAA;EAGlB,OAAAkD;AACE,WAAOtD,KAAKI,IAAI,UAAA;EAAA;EAGV,IAAImD,IAAAA;AAEV,WADAvD,KAAKC,MAAMgD,KAAKM,EAAAA,GACTvD;EAAAA;EAGT,WAAAwD;AACE,WAAOxD,KAAKC,MAAMwD,KAAK,EAAA;EAAA;EAGzB,WAAAC;AACE,WAAO,IAAIC,OAAO3D,KAAKwD,SAAAA,GAAY,CAAA,GAAIxD,KAAKE,KAAAA,EAAOuD,KAAK,EAAA,CAAA;EAAA;AAAA;AAWtD,IAAAG,IAAc,MAAe,IAAI9D;AAAjC,IAEA+D,KAAW,MAAA;AACf,QAAMC,KACJC,CAAAA,OAAAA;AAEA,UAAMC,KAAQD,GAAAA,EAAUL,SAAAA;AACxB,WAAO,MAAM,IAAIC,OAAOK,GAAMC,QAAQD,GAAM9D,KAAAA;EAAM;AAGpD,SAAO,EACLgE,OAAOJ,IAAoB,MACzBF,EAAAA,EACGzB,YAAAA,EACA7B,KAAAA,EACAV,UAAAA,EACAuE,QAAQ,GAAA,EACR7D,KAAAA,EACAV,UAAAA,EACAkC,WAAAA,EACAqC,QAAQ,GAAA,EACR7D,KAAAA,EACAV,UAAAA,EACAsC,SAAAA,EACAvC,WAAAA,EACAwE,QAAQ,GAAA,EACR5E,OAAAA,EACA6E,QAAQ,CAAA,EACRhC,UAAAA,EAAAA,GAELiC,KAAKP,IAAoB,MACvBF,EAAAA,EACGzB,YAAAA,EACAgB,SAAAA,EACAC,IAAAA,EACA9C,KAAAA,EACAV,UAAAA,EACAuE,QAAQ,GAAA,EACRd,IAAAA,EACAC,KAAAA,EACAlB,UAAAA,EAAAA,GAELkC,oBAAoBR,IAAoB,MACtCF,EAAAA,EACGzB,YAAAA,EACAgC,QAAQ,GAAA,EACR/E,MAAAA,EACAmF,QAAQ,GAAG,CAAA,EACXJ,QAAQ,GAAA,EACR/E,MAAAA,EACAmF,QAAQ,GAAG,EAAA,EACXnC,UAAAA,EAAAA,EAAAA;AAGR,GApDgB;;;ACxTV,IAAM,gBAAgB,EAAY,EACtC,QAAQ,KAAK,EAAE,QAAQ,EACvB,kBAAkB,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EACpE,QAAQ,EAAE,QAAQ,KAAK,EACvB,OAAO,EAAE,SAAS;AAEd,IAAM,wBAAwB,CAAC,YAA4B,EAAY,EAC3E,YAAY,EACZ,QAAQ,OAAO,EACf,QAAQ,GAAG,EACX,MAAM,MAAM,EAAE,WAAW,EACzB,kBAAkB,EAChB,kBAAkB,EAChB,SAAS,MAAM,EAAE,UAAU,EAC7B,SAAS,EACT,WAAW,EACT,QAAQ,GAAG,EACX,WAAW,EAAE,WAAW,EACxB,kBAAkB,EAChB,aAAa,EAAE,UAAU,EAC3B,SAAS,EACX,SAAS,EAAE,SAAS,EACtB,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS;AAEZ,IAAM,cAAc;AACpB,IAAM,oBAAoB;AAEnB,IAAM,iCAAiC,EAAY,EACvD,QAAQ,GAAG,EACX,gBAAgB,qBAAqB,EACnC,MAAM,QAAQ,EAAE,WAAW,EAC7B,SAAS,EAAE,SAAS,EACpB,gBAAgB,wBAAwB,EACtC,QAAQ,IAAI,EACd,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,WAAW,EACT,gBAAgB,iBAAiB,EAC/B,SAAS,WAAW,EAAE,UAAU,EAChC,WAAW,EACT,WAAW,EAAE,UAAU,EAAE,SAAS,WAAW,EAAE,UAAU,EAC3D,SAAS,EAAE,UAAU,EACvB,SAAS,EACT,kBAAkB,mCAAmC,EACvD,SAAS,EACT,GAAG,EACH,gBAAgB,iBAAiB,EAC/B,SAAS,WAAW,EAAE,WAAW,EACjC,SAAS,QAAM,WAAW,EAC5B,SAAS,EACX,SAAS,EACT,WAAW,EACT,QAAQ,GAAG,EACX,gBAAgB,4BAA4B,EAC1C,QAAQ,GAAG,EAAE,QAAQ,CAAC,EACxB,SAAS,EAAE,SAAS,EACpB,gBAAgB,oBAAoB,EAClC,WAAW,EACT,SAAS,KAAK,EAAE,UAAU,EAC5B,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,QAAQ,GAAG,EACX,SAAS,IAAI,EAAE,UAAU,EAAE,KAAK,EAClC,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,QAAQ,GAAG,EACX,SAAS,GAAG,EAAE,UAAU,EAAE,KAAK,EACjC,SAAS,EAAE,WAAW,EACxB,SAAS,EACT,QAAQ,GAAG,EACb,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,QAAQ,GAAG,EACX,gBAAgB,uBAAuB,EACrC,SAAS,GAAG,EAAE,UAAU,EAAE,KAAK,EACjC,SAAS,EACT,QAAQ,GAAG,EACb,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,QAAQ,GAAG,EACX,gBAAgB,gBAAgB,EAC9B,SAAS,KAAK,EAAE,UAAU,EAAE,KAAK,EACnC,SAAS,EACT,QAAQ,GAAG,EACb,SAAS,EAAE,SAAS,EACpB,gBAAgB,uBAAuB,EACrC,WAAW,EACT,QAAQ,GAAG,EACX,WAAW,EACT,MAAM,QAAQ,EAAE,WAAW,EAC7B,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,QAAQ,IAAI,EACd,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,WAAW,EACT,WAAW,EACT,SAAS,WAAW,EAAE,UAAU,EAChC,WAAW,EACT,WAAW,EAAE,UAAU,EAAE,SAAS,WAAW,EAAE,UAAU,EAC3D,SAAS,EAAE,UAAU,EACvB,SAAS,EACT,kBAAkB,mCAAmC,EACvD,SAAS,EACT,GAAG,EACH,WAAW,EACT,SAAS,WAAW,EAAE,UAAU,EAClC,SAAS,EACX,SAAS,EACT,WAAW,EACT,QAAQ,GAAG,EACX,WAAW,EACT,QAAQ,GAAG,EAAE,QAAQ,CAAC,EACxB,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,SAAS,IAAI,EAAE,UAAU,EAC3B,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,QAAQ,GAAG,EACX,WAAW,EACT,SAAS,GAAG,EAAE,UAAU,EAAE,KAAK,EACjC,SAAS,EACX,SAAS,EAAE,SAAS,EACpB,QAAQ,GAAG,EACb,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,QAAQ,GAAG,EACX,WAAW,EACT,SAAS,GAAG,EAAE,UAAU,EAAE,KAAK,EACjC,SAAS,EACT,QAAQ,GAAG,EACb,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,QAAQ,GAAG,EACX,WAAW,EACT,SAAS,KAAK,EAAE,UAAU,EAAE,KAAK,EACnC,SAAS,EACT,QAAQ,GAAG,EACb,SAAS,EAAE,SAAS,EACtB,SAAS,EAAE,WAAW,EACxB,SAAS,EACT,SAAS;AAEL,IAAM,oCAAoC,IAAI,OAAO,QAAQ,+BAA+B,OAAO,MAAM,CAAC,CAAC;AAE3G,IAAM,2BAA2B,EAAY,EACjD,gBAAgB,UAAU,EACxB,SAAS,KAAK,EAAE,UAAU,EAC5B,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,QAAQ,GAAG,EACX,gBAAgB,MAAM,EACpB,SAAS,IAAI,EAAE,UAAU,EAC3B,SAAS,EACX,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,QAAQ,GAAG,EACX,gBAAgB,aAAa,EAC3B,WAAW,EACT,SAAS,GAAG,EAAE,UAAU,EAC1B,SAAS,EAAE,WAAW,EACxB,SAAS,EACX,SAAS,EAAE,SAAS,EACpB,SAAS;AAEL,IAAM,8BAA8B,EAAY,EACpD,QAAQ,IAAI,EACZ,gBAAgB,qBAAqB,EACnC,SAAS,iBAAiB,EAAE,UAAU,EACxC,SAAS,EACT,QAAQ,GAAG,EACX,gBAAgB,sBAAsB,EACpC,MAAM,QAAQ,EAAE,WAAW,EAC7B,SAAS,EAAE,SAAS,EACpB,gBAAgB,yBAAyB,EACvC,QAAQ,IAAI,EACd,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,WAAW,EACT,gBAAgB,kBAAkB,EAChC,SAAS,WAAW,EAAE,UAAU,EAChC,WAAW,EACT,WAAW,EAAE,UAAU,EAAE,SAAS,WAAW,EAAE,UAAU,EAC3D,SAAS,EAAE,UAAU,EACvB,SAAS,EACT,kBAAkB,mCAAmC,EACvD,SAAS,EACT,GAAG,EACH,gBAAgB,kBAAkB,EAChC,SAAS,WAAW,EAAE,WAAW,EACjC,SAAS,QAAM,WAAW,EAC5B,SAAS,EACX,SAAS,EACT,WAAW,EACT,QAAQ,GAAG,EACX,gBAAgB,6BAA6B,EAC3C,QAAQ,GAAG,EAAE,QAAQ,CAAC,EACxB,SAAS,EAAE,SAAS,EACpB,gBAAgB,qBAAqB,EACnC,WAAW,EACT,SAAS,KAAK,EAAE,UAAU,EAC5B,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,QAAQ,GAAG,EACX,SAAS,IAAI,EAAE,UAAU,EAAE,KAAK,EAClC,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,QAAQ,GAAG,EACX,SAAS,GAAG,EAAE,UAAU,EAAE,KAAK,EACjC,SAAS,EAAE,WAAW,EACxB,SAAS,EACT,QAAQ,GAAG,EACb,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,QAAQ,GAAG,EACX,gBAAgB,wBAAwB,EACtC,SAAS,GAAG,EAAE,UAAU,EAAE,KAAK,EACjC,SAAS,EACT,QAAQ,GAAG,EACb,SAAS,EAAE,SAAS,EACpB,SAAS;AAEL,IAAM,uBAAuB,EAAY,EAC7C,YAAY,EACZ,gBAAgB,oBAAoB,EAClC,SAAS,GAAG,EAAE,UAAU,EAC1B,SAAS,EACT,QAAQ,GAAG,EACX,gBAAgB,uBAAuB,EACrC,SAAS,GAAG,EAAE,UAAU,EAC1B,SAAS,EACT,UAAU,EACV,SAAS;AAEL,IAAM,gBAAgB,EAAY,EACtC,QAAQ,GAAG,EACX,gBAAgB,mBAAmB,EACjC,MAAM,OAAO,EAAE,WAAW,EAC5B,SAAS,EACT,WAAW,EACT,WAAW,EACT,gBAAgB,eAAe,EAC7B,SAAS,WAAW,EAAE,UAAU,EAChC,WAAW,EACT,WAAW,EAAE,UAAU,EAAE,SAAS,WAAW,EAAE,UAAU,EAC3D,SAAS,EAAE,UAAU,EACvB,SAAS,EAAE,kBAAkB,uBAAuB,EACtD,SAAS,EACT,GAAG,EACH,gBAAgB,eAAe,EAC7B,SAAS,WAAW,EAAE,WAAW,EACjC,SAAS,QAAM,WAAW,EAC5B,SAAS,EACX,SAAS,EACT,WAAW,EACT,QAAQ,GAAG,EACX,gBAAgB,kBAAkB,EAChC,aAAa,EAAE,WAAW,EAAE,KAAK,EACnC,SAAS,EACT,QAAQ,GAAG,EACb,SAAS,EAAE,SAAS,EACpB,SAAS;AAEZ,IAAM,aAAa,EAAY,EAC5B,QAAQ,GAAG,EACX,gBAAgB,WAAW,EACzB,aAAa,EAAE,WAAW,EAAE,KAAK,EACnC,SAAS,EACT,QAAQ,GAAG,EACX,gBAAgB,eAAe,EAC7B,aAAa,EAAE,UAAU,EAAE,KAAK,EAClC,SAAS,EACT,WAAW,EACT,QAAQ,GAAG,EACX,gBAAgB,WAAW,EACzB,aAAa,EAAE,UAAU,EAAE,KAAK,EAClC,SAAS,EACX,SAAS,EAAE,SAAS,EACpB,QAAQ,GAAG,EACX,SAAS;AAEL,IAAM,yBAAyB,EAAY,EAC/C,QAAQ,IAAI,EACZ,WAAW,EACT,gBAAgB,eAAe,EAC7B,SAAS,KAAK,EAAE,UAAU,EAC5B,SAAS,EACT,QAAQ,GAAG,EACb,SAAS,EAAE,SAAS,EACpB,gBAAgB,mBAAmB,EACjC,WAAW,EACT,SAAS,KAAK,EAAE,UAAU,EAC5B,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,QAAQ,GAAG,EACX,SAAS,IAAI,EAAE,UAAU,EAAE,KAAK,EAClC,SAAS,EAAE,SAAS,EACpB,WAAW,EACT,QAAQ,GAAG,EACX,SAAS,GAAG,EAAE,UAAU,EAAE,KAAK,EACjC,SAAS,EAAE,WAAW,EACxB,SAAS,EACT,QAAQ,IAAI,EACZ,SAAS;AAEL,IAAM,cAAc,IAAI;AAAA,EAC7B;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EACG,IAAI,CAACoC,OAAMA,GAAE,MAAM,EACnB,KAAK,GAAG;AAAA,EACX;AACF;AAEO,IAAM,eAAe,EAAY,EACrC,QAAQ,IAAI,EACZ,aAAa,EAAE,WAAW,EAC1B,OAAO,EACP,SAAS;AAEL,IAAM,oBAAoB,EAAY,EAC1C,QAAQ,IAAI,EACZ,aAAa,EAAE,WAAW,EAAE,KAAK,EACjC,QAAQ,IAAI,EACZ,WAAW,EAAE,WAAW,EACxB,OAAO,EACP,SAAS;AAEL,IAAM,oBAAoB,EAAY,EAC1C,QAAQ,GAAG,EACX,gBAAgB,MAAM,EACpB,aAAa,EAAE,UAAU,EAC3B,SAAS,EACT,QAAQ,GAAG,EACX,QAAQ,EACR,gBAAgB,OAAO,EACrB,aAAa,EAAE,WAAW,EAAE,KAAK,EACnC,SAAS,EACT,WAAW,EACT,QAAQ,EAAE,QAAQ,EAChB,GAAG,EACL,UAAU,EACZ,SAAS,EACT,OAAO,EACP,SAAS;AAEL,IAAM,aAAa,EAAY,EACnC,YAAY,EACZ,MAAM,EAAE,UAAU,EAClB,WAAW,EACX,MAAM,KAAK,EAAE,QAAQ,CAAC,EACpB,MAAM,EAAE,UAAU,EACpB,SAAS,EAAE,SAAS,EACpB,QAAQ,GAAG,EACX,MAAM,EAAE,UAAU,EAClB,WAAW,EACT,MAAM,KAAK,EAAE,QAAQ,CAAC,EACtB,MAAM,EAAE,UAAU,EACpB,SAAS,EAAE,SAAS,EACpB,UAAU,EACV,SAAS;AAEL,IAAM,kBAAkB,EAAY,EACxC,YAAY,EACZ,MAAM,EAAE,UAAU,EAClB,WAAW,EACT,MAAM,KAAK,EAAE,QAAQ,CAAC,EACtB,MAAM,EAAE,UAAU,EACpB,SAAS,EAAE,SAAS,EACpB,UAAU,EACV,SAAS;AAEL,IAAM,aAAa,EAAY,EACnC,YAAY,EACZ,MAAM,EAAE,UAAU,EAClB,WAAW,EACT,MAAM,GAAG,EAAE,QAAQ,CAAC,EACpB,MAAM,EAAE,UAAU,EACpB,SAAS,EAAE,SAAS,EACpB,UAAU,EACV,SAAS;;;AClYL,IAAM,QAA0B;AAAA;AAAA,EAErC;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,CAAC,QAAQ,SAAS,SAAS;AAAA,IACpC,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,CAAC,YAAY,aAAa,eAAe,SAAS,MAAM;AAAA,IACjE,QAAQ;AAAA,EACV;AAAA;AAAA,EAEA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,CAAC,SAAS,QAAQ;AAAA,IAC3B,QAAQ;AAAA;AAAA,IACR,gBAAgB,EAAE,IAAI,SAAS,IAAI,QAAQ;AAAA,IAC3C,UAAU;AAAA;AAAA,IACV,WAAW,EAAE,SAAS,MAAM,cAAc,CAAC,CAAC,EAAE;AAAA,EAChD;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,CAAC,SAAS,QAAQ;AAAA,IAC3B,QAAQ;AAAA;AAAA,IACR,gBAAgB,EAAE,IAAI,SAAS,IAAI,QAAQ;AAAA,IAC3C,WAAW,EAAE,SAAS,MAAM,cAAc,CAAC,GAAG,CAAC,EAAE;AAAA,EACnD;AAAA;AAAA,EAGA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,CAAC,cAAc,eAAe,cAAc,eAAe,IAAI;AAAA,IACxE,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,CAAC,cAAc,eAAe,cAAc,aAAa;AAAA,IAClE,QAAQ;AAAA,IACR,YAAY;AAAA;AAAA,EACd;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,CAAC,aAAa,cAAc,aAAa,YAAY;AAAA,IAC9D,QAAQ;AAAA,IACR,YAAY;AAAA;AAAA,EACd;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,CAAC,SAAS,UAAU,SAAS,QAAQ;AAAA,IAC9C,QAAQ;AAAA,EACV;AAAA;AAAA,EAGA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,CAAC,OAAO,OAAO,UAAK,UAAU;AAAA,IACvC,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AAAA;AAAA,EAGA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,CAAC,YAAY,WAAW;AAAA,IACjC,QAAQ;AAAA;AAAA,IACR,gBAAgB,EAAE,QAAQ,GAAG,IAAI,OAAO,IAAI,OAAO,IAAI,EAAE;AAAA,IACzD,UAAU;AAAA;AAAA,IACV,WAAW,EAAE,SAAS,MAAM,cAAc,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE;AAAA,EACzD;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,CAAC,cAAc,aAAa;AAAA,IACrC,QAAQ;AAAA;AAAA,IACR,gBAAgB,EAAE,QAAQ,IAAI,IAAI,QAAQ,IAAI,QAAQ,IAAI,GAAG;AAAA,IAC7D,UAAU;AAAA;AAAA,IACV,WAAW,EAAE,SAAS,KAAK;AAAA,EAC7B;AAAA;AAAA,EAGA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,CAAC,eAAe,cAAc;AAAA,IACvC,QAAQ;AAAA;AAAA,IACR,gBAAgB,EAAE,IAAI,SAAS,IAAI,QAAQ;AAAA,IAC3C,UAAU;AAAA;AAAA,IACV,WAAW,EAAE,SAAS,MAAM,cAAc,CAAC,CAAC,EAAE;AAAA,EAChD;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,CAAC,MAAM;AAAA,IAChB,QAAQ;AAAA;AAAA,IACR,gBAAgB,EAAE,IAAI,SAAS,IAAI,QAAQ;AAAA,IAC3C,UAAU;AAAA;AAAA,IACV,WAAW,EAAE,SAAS,KAAK;AAAA,EAC7B;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,CAAC,OAAO;AAAA,IACjB,QAAQ;AAAA;AAAA,IACR,gBAAgB,EAAE,IAAI,SAAS,IAAI,QAAQ;AAAA,IAC3C,UAAU;AAAA;AAAA,IACV,WAAW,EAAE,SAAS,MAAM,cAAc,CAAC,CAAC,EAAE;AAAA,IAC9C,YAAY;AAAA;AAAA,EACd;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,CAAC,QAAQ;AAAA,IAClB,QAAQ;AAAA;AAAA,IACR,gBAAgB,EAAE,IAAI,SAAS,IAAI,QAAQ;AAAA,IAC3C,UAAU;AAAA;AAAA,IACV,WAAW,EAAE,SAAS,MAAM,cAAc,CAAC,CAAC,EAAE;AAAA,IAC9C,YAAY;AAAA;AAAA,EACd;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,CAAC,SAAS;AAAA,IACnB,QAAQ;AAAA;AAAA,IACR,gBAAgB,EAAE,IAAI,SAAS,IAAI,QAAQ;AAAA,IAC3C,WAAW,EAAE,SAAS,MAAM,cAAc,CAAC,CAAC,EAAE;AAAA,EAChD;AAAA;AAAA,EAGA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,CAAC,UAAU,IAAI;AAAA,IACxB,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AACF;AAEA,IAAM,UAAU,oBAAI,IAA4B;AAChD,WAAW,QAAQ,OAAO;AACxB,UAAQ,IAAI,KAAK,KAAK,YAAY,GAAG,IAAI;AACzC,aAAW,SAAS,KAAK,SAAS;AAChC,YAAQ,IAAI,MAAM,YAAY,GAAG,IAAI;AAAA,EACvC;AACF;AAEO,SAAS,cAAc,OAAe,IAAgC;AAC3E,SAAO,QAAQ,IAAI,KAAK,YAAY,EAAE,KAAK,CAAC;AAC9C;AAEO,IAAM,UAAU;AAEhB,SAAS,YACd,OAAe,SACf,mBAA4B,OACR;AACpB,QAAM,iBAAiB,cAAc,IAAI;AACzC,QAAM,eAAmC,iBACrC,EAAE,GAAG,gBAAgB,KAAK,IAC1B,EAAE,MAAM,MAAM,SAAS,QAAQ,OAAO;AAC1C,SAAO,mBACH,EAAE,GAAG,cAAc,kBAAkB,KAAK,IAC1C;AACN;AAEO,SAAS,SAAS,MAAoC;AAC3D,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,YAAY,KAAK,IAAI,EAAE,SAAS;AACzC;;;ACxMA,IAAAC,cAAgB;;;ACAhB,iBAAgB;AAUT,IAAM,uBAAuB,CAAC,GAAG,GAAG,CAAC;AAErC,IAAM,4BAA4B;AAElC,IAAM,oBAAoB;AAEjC,SAAS,IAAIC,IAAW,GAAmB;AACzC,SAAO,MAAM,IAAIA,KAAI,IAAI,GAAGA,KAAI,CAAC;AACnC;AAEO,SAAS,iBACd,KACA,KAC8B;AAC9B,MAAI,QAAQ,GAAG;AACb,UAAM,IAAI,MAAM,6BAA6B;AAAA,EAC/C;AAEA,QAAM,gBAAgB,IAAI,KAAK,IAAI,GAAG,GAAG,KAAK,IAAI,GAAG,CAAC;AACtD,MAAI,gBAAgB,MAAM;AAC1B,MAAI,gBAAgB,MAAM;AAC1B,MAAI,gBAAgB,GAAG;AACrB,oBAAgB,CAAC;AACjB,oBAAgB,CAAC;AAAA,EACnB;AAEA,MAAI,kBAAkB,GAAG;AACvB,WAAO,EAAE,MAAM,WAAW,SAAS,cAAc;AAAA,EACnD,OAAO;AACL,WAAO,EAAE,MAAM,YAAY,KAAK,eAAe,KAAK,cAAc;AAAA,EACpE;AACF;AAYO,SAAS,oBACd,OACA,eAAyB,sBACzB,WAAmB,2BACnB,WAAmB,mBACG;AAEtB,MAAI,SAAS,KAAK,CAAC,OAAO,SAAS,KAAK,GAAG;AACzC,WAAO;AAAA,EACT;AAGA,QAAM,YAAY,KAAK,MAAM,KAAK;AAClC,MAAI,YAAY,UAAU;AACxB,WAAO;AAAA,EACT;AAGA,QAAM,iBAAiB,QAAQ;AAC/B,MAAI,iBAAiB,MAAM;AACzB,WAAO;AAAA,EACT;AAEA,MAAI,eAAmE;AAEvE,aAAW,OAAO,cAAc;AAE9B,UAAM,WAAW,QAAQ;AACzB,UAAM,aAAa,KAAK,MAAM,QAAQ;AAGtC,QAAI,eAAe,EAAG;AAEtB,UAAM,oBAAoB,aAAa;AACvC,UAAM,gBAAgB,KAAK,IAAI,oBAAoB,KAAK,IAAI;AAG5D,QAAI,iBAAiB,UAAU;AAG7B,UAAI,CAAC,gBAAgB,gBAAgB,aAAa,OAAO;AACvD,uBAAe,EAAE,KAAK,YAAY,KAAK,OAAO,cAAc;AAAA,MAC9D;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,cAAc;AACjB,WAAO;AAAA,EACT;AAGA,QAAM,gBAAgB,IAAI,aAAa,KAAK,aAAa,GAAG;AAC5D,SAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK,aAAa,MAAM;AAAA,IACxB,KAAK,aAAa,MAAM;AAAA,EAC1B;AACF;AAEO,SAAS,gBAAgB,GAAyC;AACvE,MAAI,EAAE,SAAS,WAAW;AACxB,WAAO,EAAE;AAAA,EACX;AACA,SAAO,EAAE,MAAM,EAAE;AACnB;AAEO,SAAS,qBACd,GACA,QAC8B;AAC9B,MAAI,EAAE,SAAS,WAAW;AACxB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAS,WAAAC,SAAI,EAAE,OAAO,EAAE,MAAM,MAAM,EAAE,SAAS;AAAA,IACjD;AAAA,EACF;AACA,SAAO,qBAAiB,WAAAA,SAAI,EAAE,GAAG,EAAE,MAAM,MAAM,EAAE,SAAS,GAAG,EAAE,GAAG;AACpE;AAEO,SAAS,iBACd,MACA,MAC8B;AAC9B,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,MAAI,KAAK,SAAS,WAAW;AAC3B,WAAO,KAAK;AACZ,WAAO;AAAA,EACT,OAAO;AACL,WAAO,KAAK;AACZ,WAAO,KAAK;AAAA,EACd;AAEA,MAAI,KAAK,SAAS,WAAW;AAC3B,WAAO,KAAK;AACZ,WAAO;AAAA,EACT,OAAO;AACL,WAAO,KAAK;AACZ,WAAO,KAAK;AAAA,EACd;AAGA,MAAI,SAAS,KAAK,SAAS,GAAG;AAC5B,WAAO,EAAE,MAAM,WAAW,SAAS,EAAE;AAAA,EACvC;AAGA,MACG,KAAK,SAAS,cAAc,KAAK,SAAS,cAC1C,KAAK,SAAS,cACb,KAAK,SAAS,aACd,KAAK,YAAY,KAClB,KAAK,SAAS,cAAc,KAAK,SAAS,aAAa,KAAK,YAAY,GACzE;AACA,UAAM,YAAY,OAAO;AACzB,UAAM,SAAS,OAAO,OAAO,OAAO;AACpC,WAAO,iBAAiB,QAAQ,SAAS;AAAA,EAC3C,OAAO;AACL,WAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAS,WAAAA,SAAI,IAAI,EAAE,IAAI,IAAI,EAAE,QAAI,WAAAA,SAAI,IAAI,EAAE,IAAI,IAAI,CAAC,EAAE,SAAS;AAAA,IACjE;AAAA,EACF;AACF;AASO,IAAM,mBAAmB,CAC9B,GACA,YAAoB,MACH;AACjB,QAAM,QAAQ,EAAE,SAAS,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE;AAG3D,MAAI,UAAU,GAAG;AACf,WAAO,EAAE,MAAM,WAAW,SAAS,EAAE;AAAA,EACvC;AAEA,QAAM,WAAW,KAAK,IAAI,KAAK;AAG/B,MAAI,YAAY,KAAM;AACpB,WAAO,EAAE,MAAM,WAAW,SAAS,KAAK,MAAM,KAAK,EAAE;AAAA,EACvD;AAGA,QAAM,YAAY,KAAK,MAAM,KAAK,MAAM,QAAQ,CAAC;AACjD,QAAM,QAAQ,KAAK,IAAI,IAAI,YAAY,IAAI,SAAS;AACpD,QAAM,UAAU,KAAK,MAAM,QAAQ,KAAK,IAAI;AAE5C,SAAO,EAAE,MAAM,WAAW,SAAS,QAAQ;AAC7C;AAWO,IAAM,oBAAoB,CAC/B,OACA,SACA,YAAoB,MACa;AAEjC,MAAI,QAAQ,WAAW,SAAS;AAC9B,UAAM,eAAe,QAAQ,UAAU,gBAAgB;AACvD,UAAM,WAAW,QAAQ,UAAU,YAAY;AAE/C,UAAM,WAAW;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI,UAAU;AACZ,aAAO;AAAA,IACT;AAAA,EACF;AAGA,SAAO,iBAAiB,EAAE,MAAM,WAAW,SAAS,MAAM,GAAG,SAAS;AACxE;AAEO,SAAS,sBACd,OACA,QACoB;AACpB,MAAI,MAAM,SAAS,SAAS;AAC1B,UAAM,WAAW;AAAA,MACf,MAAM;AAAA,UACN,WAAAA,SAAI,MAAM;AAAA,IACZ;AACA,QACE,SAAS,SAAS,mBACjB,WAAAA,SAAI,MAAM,EAAE,SAAS,MAAM,aAAS,WAAAA,SAAI,MAAM,EAAE,SAAS,CAAC;AAAA,QACzD,WAAAA,SAAI,CAAC,EAAE,IAAI,MAAM,EAAE,SAAS,MAC1B,aAAS,WAAAA,SAAI,CAAC,EAAE,IAAI,MAAM,EAAE,SAAS,CAAC,IAC1C;AAEA,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO,iBAAiB,QAAQ;AAAA,IAClC;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK,qBAAqB,MAAM,KAAK,MAAM;AAAA,IAC3C,KAAK,qBAAqB,MAAM,KAAK,MAAM;AAAA,EAC7C;AACF;AAEO,SAAS,gBAAgB,GAAwC;AACtE,MAAI,EAAE,SAAS,SAAS;AACtB,WAAO,EAAE,MAAM,SAAS,SAAS,EAAE,MAAM,OAAO,gBAAgB,EAAE,KAAK;AAAA,EACzE,OAAO;AACL,YAAQ,gBAAgB,EAAE,GAAG,IAAI,gBAAgB,EAAE,GAAG,KAAK;AAAA,EAC7D;AACF;;;ACrRO,SAAS,kBACd,IACA,IACS;AACT,MAAI,GAAG,SAAS,GAAG,MAAM;AACvB,WAAO;AAAA,EACT;AACA,MAAI,GAAG,SAAS,WAAW,GAAG,SAAS,SAAS;AAC9C,WAAO;AAAA,EACT;AAEA,MAAI,GAAG,SAAS,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ;AAClD,WAAO;AAAA,EACT;AAGA,MAAI,GAAG,SAAS,GAAG,MAAM;AAEvB,QACE,GAAG,WAAW,eACd,GAAG,WAAW,YACd,GAAG,gBAAgB,WAAW,QAC9B;AACA,aAAO;AAAA,IACT;AACA,QACE,GAAG,WAAW,eACd,GAAG,WAAW,YACd,GAAG,gBAAgB,WAAW,QAC9B;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAYO,SAAS,oBACd,IACA,IACS;AACT,MAAI,GAAG,SAAS,GAAG,KAAM,QAAO;AAChC,MAAI,GAAG,SAAS,WAAW,GAAG,SAAS,QAAS,QAAO;AAEvD,SAAO,GAAG,SAAS,GAAG;AACxB;AAQO,SAAS,2BACd,MACA,QACS;AACT,MAAI,KAAK,WAAW,OAAQ,QAAO;AACnC,MAAI,KAAK,WAAW,aAAa;AAG/B,QAAI,KAAK,gBAAgB;AACvB,aAAO,UAAU,KAAK;AAAA,IACxB;AAGA,QAAI,WAAW,SAAU,QAAO;AAAA,EAClC;AAEA,MAAI,KAAK,WAAW,YAAY,WAAW,MAAM;AAC/C,WAAO;AAAA,EACT;AACA,SAAO;AACT;;;AFjFA,IAAM,UAAU;AAChB,IAAM,oBAAoB;AAK1B,SAAS,iBAAiB,OAAwB;AAChD,SAAO,KAAK,IAAI,QAAQ,KAAK,MAAM,KAAK,CAAC,IAAI;AAC/C;AAMA,SAAS,YAAY,MAA8B;AACjD,SAAO,KAAK,YAAY;AAC1B;AAQA,SAAS,eAAe,OAAe,MAA+B;AACpE,QAAM,WAAW,YAAY,IAAI;AAGjC,MAAI,SAAS,KAAK,SAAS,UAAU;AACnC,WAAO;AAAA,EACT;AAGA,MAAI,QAAQ,KAAK,QAAQ,KAAK,KAAK,WAAW,SAAS;AACrD,UAAM,eAAe,KAAK,UAAU,gBAAgB;AACpD,UAAM,WAAW,KAAK,UAAU,YAAY;AAC5C,UAAM,WAAW;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,WAAO,aAAa;AAAA,EACtB;AAEA,SAAO;AACT;AAkBO,SAAS,aACd,aACA,UACA,QACA,YACyC;AACzC,QAAM,iBAAiB,IAAI,IAAI,WAAW,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAE5D,QAAM,aAAa,MAAM;AAAA,IACvB,CAAC,MACC,EAAE,SAAS,YACX,2BAA2B,GAAG,MAAM,MACnC,EAAE,eAAe,SAAS,eAAe,IAAI,EAAE,IAAI;AAAA,EACxD;AAGA,MAAI,WAAW,WAAW,GAAG;AAE3B,UAAM,eAAe,WAAW,CAAC;AACjC,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO,cAAc,UAAU,cAAc,MAAM;AAAA,IACrD;AAAA,EACF;AAIA,QAAM,uBAAuB,WAAW,IAAI,CAAC,UAAU;AAAA,IACrD;AAAA,IACA,OAAO,cAAc,UAAU,MAAM,MAAM;AAAA,EAC7C,EAAE;AAGF,QAAM,UAAU,qBAAqB;AAAA,IAAO,CAAC,MAC3C,eAAe,EAAE,OAAO,EAAE,IAAI;AAAA,EAChC;AAEA,MAAI,QAAQ,SAAS,GAAG;AAEtB,UAAM,wBAAwB,QAAQ;AAAA,MACpC,CAAC,MAAM,iBAAiB,EAAE,KAAK,KAAK,eAAe,IAAI,EAAE,KAAK,IAAI;AAAA,IACpE;AACA,QAAI,sBAAsB,SAAS,GAAG;AAEpC,aAAO,sBAAsB,KAAK,CAACC,IAAG,MAAMA,GAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAAA,IAClE;AAGA,UAAM,cAAc,QAAQ,OAAO,CAAC,MAAM,iBAAiB,EAAE,KAAK,CAAC;AACnE,QAAI,YAAY,SAAS,GAAG;AAE1B,aAAO,YAAY,KAAK,CAACA,IAAG,MAAMA,GAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAAA,IACxD;AAGA,WAAO,QAAQ,KAAK,CAACA,IAAG,MAAM;AAE5B,YAAM,YAAY,eAAe,IAAIA,GAAE,KAAK,IAAI,IAAI,IAAI;AACxD,YAAM,YAAY,eAAe,IAAI,EAAE,KAAK,IAAI,IAAI,IAAI;AACxD,UAAI,cAAc,UAAW,QAAO,YAAY;AAEhD,aAAOA,GAAE,QAAQ,EAAE;AAAA,IACrB,CAAC,EAAE,CAAC;AAAA,EACN;AAEA,SAAO,qBAAqB,KAAK,CAACA,IAAG,MAAM;AACzC,UAAM,YAAY,YAAYA,GAAE,IAAI;AACpC,UAAM,YAAY,YAAY,EAAE,IAAI;AACpC,UAAM,YAAYA,GAAE,QAAQ,IAAI,IAAIA,GAAE,QAAQA,GAAE,QAAQ;AACxD,UAAM,YAAY,EAAE,QAAQ,IAAI,IAAI,EAAE,QAAQ,EAAE,QAAQ;AACxD,WAAO,YAAY;AAAA,EACrB,CAAC,EAAE,CAAC;AACN;AAEO,SAAS,aAAa,IAAyB,IAAyB;AAC7E,QAAM,UAAU,gBAAgB,GAAG,QAAQ;AAC3C,QAAM,UAAU,gBAAgB,GAAG,QAAQ;AAC3C,QAAM,SACJ,YAAY,GAAG,QAAQ,YAAY,GAAG,OAClC,GAAG,KAAK,SAAS,GAAG,KAAK,SACzB;AAEN,MAAI,OAAO,YAAY,YAAY,OAAO,YAAY,UAAU;AAC9D,UAAM;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AACA,aAAO,YAAAC,SAAI,OAAO,EAAE,MAAM,MAAM,EAAE,IAAI,OAAO;AAC/C;AAEO,SAAS,iBACd,GACA,MACA;AACA,MAAI,YAAY,EAAE,QAAQ,YAAY,KAAK,MAAM;AAC/C,WAAO,EAAE,KAAK,SAAS,KAAK,KAAK;AAAA,EACnC,OAAO;AACL,WAAO;AAAA,EACT;AACF;AAaO,SAAS,UACd,MACA,QACQ;AACR,MAAI,KAAK,WAAW,eAAe,UAAU,KAAK,gBAAgB;AAChE,WAAO,KAAK,eAAe,MAAM,KAAK,KAAK;AAAA,EAC7C;AACA,SAAO,KAAK;AACd;;;AGnMO,IAAM,uCAAN,cAAmD,MAAM;AAAA,EAC9D,YACE,WACA,WACA,cACA;AACA;AAAA,MACE,kBAAkB,SAAS,KAAK,SAAS,4BAA4B,YAAY;AAAA,sDACjC,SAAS,eAAe,YAAY,eAAe,YAAY,2CAA2C,SAAS;AAAA,IACrK;AACA,SAAK,OAAO;AAAA,EACd;AACF;AAMO,IAAM,+BAAN,cAA2C,MAAM;AAAA,EACtD,cAAc;AACZ;AAAA,MACE;AAAA,IACF;AACA,SAAK,OAAO;AAAA,EACd;AACF;AAMO,IAAM,6BAAN,cAAyC,MAAM;AAAA,EACpD,cAAc;AACZ;AAAA,MACE;AAAA,IACF;AACA,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAG7C,YAAY,WAAmB,MAA+B;AAC5D,UAAM,aAAsD;AAAA,MAC1D,mBAAmB,+EAA+E,SAAS;AAAA,MAC3G,WACE;AAAA,MACF,WAAW,cAAc,SAAS;AAAA,MAClC,YAAY,cAAc,SAAS;AAAA,MACnC,6BAA6B,gEAAgE,SAAS;AAAA,IACxG;AACA,UAAM,WAAW,IAAI,CAAC;AAXxB;AAYE,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,8BAAN,cAA0C,MAAM;AAAA,EACrD,cAAc;AACZ,UAAM,iCAAiC;AACvC,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,0BAAN,cAAsC,MAAM;AAAA,EACjD,cAAc;AACZ,UAAM,0CAA0C;AAChD,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EAChD,YAAY,OAAe,OAAe;AACxC;AAAA,MACE,6DAA6D,KAAK,QAAQ,KAAK;AAAA,IACjF;AACA,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EAC/C,YAAY,OAAe,OAAgB;AACzC;AAAA,MACE,qCAAqC,KAAK,GAAG,QAAQ,KAAK,KAAK,MAAM,EAAE;AAAA,IACzE;AACA,SAAK,OAAO;AAAA,EACd;AACF;;;AC3EO,SAAS,QAAQ,GAA6C;AACnE,SAAO,SAAS,KAAK,QAAQ;AAC/B;AACO,SAAS,UAAU,GAA+C;AACvE,SAAO,QAAQ,CAAC,KAAK,QAAQ;AAC/B;AAqBO,SAAS,WACd,GAKS;AACT,SAAO,SAAS;AAClB;AACO,SAAS,WACd,GAC2B;AAC3B,SAAO,KAAK,OAAO,MAAM,YAAY,cAAc;AACrD;AAqBO,SAAS,cACd,OACkC;AAClC,SAAO,cAAc;AACvB;AAEA,SAAS,0BAA0B,GAA0C;AAC3E,MAAI,EAAE,SAAS,UAAW,QAAO,OAAO,UAAU,EAAE,OAAO;AAE3D,SAAO,EAAE,MAAM,EAAE,QAAQ;AAC3B;AAEO,SAAS,mBAAmB,GAAgC;AACjE,MAAI,EAAE,SAAS,SAAS;AACtB,QAAI,EAAE,MAAM,SAAS,OAAQ,QAAO;AACpC,WAAO,0BAA0B,EAAE,KAAK;AAAA,EAC1C;AAEA,SAAO,0BAA0B,EAAE,GAAG,KAAK,0BAA0B,EAAE,GAAG;AAC5E;AAqBO,SAAS,gBACd,OAGA;AACA,SACE,kBAAkB,SAClB,MAAM,QAAQ,MAAM,YAAY,KAChC,MAAM,aAAa,SAAS;AAEhC;;;ACjGO,SAAS,eACd,GACuE;AACvE,MAAI,WAAW,CAAC,GAAG;AACjB,WAAO,EAAE,KAAK,EAAE,IAAI,IAAI,cAAc,EAAE;AAAA,EAC1C,WAAW,UAAU,CAAC,GAAG;AACvB,WAAO,EAAE,IAAI,EAAE,GAAG,IAAI,cAAc,EAAE;AAAA,EACxC,OAAO;AACL,UAAM,OAAiC;AAAA,MACrC,UAAU,EAAE;AAAA,IACd;AACA,QAAI,EAAE,MAAM;AACV,WAAK,OAAO,EAAE,MAAM,EAAE,KAAK;AAAA,IAC7B;AACA,WAAO;AAAA,EACT;AACF;AAEO,SAAS,kBACd,GAC6D;AAC7D,MAAI,WAAW,CAAC,GAAG;AACjB,WAAO,EAAE,KAAK,EAAE,IAAI,IAAI,iBAAiB,EAAE;AAAA,EAC7C,WAAW,UAAU,CAAC,GAAG;AACvB,WAAO,EAAE,IAAI,EAAE,GAAG,IAAI,iBAAiB,EAAE;AAAA,EAC3C,OAAO;AACL,UAAM,OAA4B;AAAA,MAChC,UAAU,EAAE;AAAA,MACZ,MAAM,YAAY,EAAE,IAAI;AAAA,IAC1B;AAEA,QAAI,EAAE,eAAe,EAAE,YAAY,SAAS,GAAG;AAC7C,YAAM,wBAAwB,EAAE,YAAY;AAAA,QAAI,CAAC,OAC/C,kBAAkB,EAAE;AAAA,MACtB;AACA,aAAO;AAAA,QACL,IAAI,CAAC,MAAM,GAAG,qBAAqB;AAAA,MACrC;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;AAQO,SAAS,0BAAsC;AACpD,SAAO,EAAE,MAAM,SAAS,OAAO,EAAE,MAAM,WAAW,SAAS,EAAE,EAAE;AACjE;AAkBO,SAAS,kBACd,IACA,IACoB;AACpB,MACG,GAAG,SAAS,WAAW,GAAG,MAAM,SAAS,UACzC,GAAG,SAAS,WAAW,GAAG,MAAM,SAAS,QAC1C;AACA,UAAM,IAAI,wBAAwB;AAAA,EACpC;AAEA,MAAI,GAAG,SAAS,WAAW,GAAG,SAAS,SAAS;AAC9C,UAAM,MAAM;AAAA,MACV,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AACA,WAAO,EAAE,MAAM,SAAS,OAAO,IAAI;AAAA,EACrC;AACA,QAAM,KACJ,GAAG,SAAS,UAAU,KAAK,EAAE,MAAM,SAAS,KAAK,GAAG,OAAO,KAAK,GAAG,MAAM;AAC3E,QAAM,KACJ,GAAG,SAAS,UAAU,KAAK,EAAE,MAAM,SAAS,KAAK,GAAG,OAAO,KAAK,GAAG,MAAM;AAC3E,QAAM,SAAS;AAAA,IACb,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AACA,QAAM,SAAS;AAAA,IACb,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AACA,SAAO,EAAE,MAAM,SAAS,KAAK,QAAQ,KAAK,OAAO;AACnD;AAiBO,SAAS,cACd,IACA,IACA,QAC0B;AAC1B,QAAM,KAAK,GAAG;AACd,QAAM,KAAK,GAAG;AAEd,MACG,GAAG,SAAS,WAAW,GAAG,MAAM,SAAS,UACzC,GAAG,SAAS,WAAW,GAAG,MAAM,SAAS,QAC1C;AACA,UAAM,IAAI,wBAAwB;AAAA,EACpC;AAEA,QAAM,WAAW,cAAc,GAAG,MAAM,IAAI;AAC5C,QAAM,WAAW,cAAc,GAAG,MAAM,IAAI;AAE5C,QAAM,8BAA8B,CAClC,MACA,MACA,UAC8B;AAAA,IAC9B,UAAU,kBAAkB,MAAM,IAAI;AAAA,IACtC;AAAA,EACF;AAIA,OACG,GAAG,MAAM,SAAS,MAAM,GAAG,SAAS,WACrC,GAAG,SAAS,QACZ;AACA,WAAO,4BAA4B,IAAI,IAAI,GAAG,IAAI;AAAA,EACpD;AACA,OACG,GAAG,MAAM,SAAS,MAAM,GAAG,SAAS,WACrC,GAAG,SAAS,QACZ;AACA,WAAO,4BAA4B,IAAI,IAAI,GAAG,IAAI;AAAA,EACpD;AAGA,MAAI,CAAC,GAAG,QAAQ,CAAC,GAAG,MAAM;AACxB,WAAO,4BAA4B,IAAI,IAAI,GAAG,IAAI;AAAA,EACpD;AACA,MACE,GAAG,QACH,GAAG,QACH,GAAG,KAAK,KAAK,YAAY,MAAM,GAAG,KAAK,KAAK,YAAY,GACxD;AAEA,QAAI,UAAU;AAEZ,YAAM,kBACJ,WACC,CAAC,UAAU,IAAI,EAAE,SAAS,SAAS,MAAM,IACrC,SAAS,SACV;AACN,aAAO,mBAAmB,IAAI,IAAI,UAAU,UAAU,iBAAiB;AAAA,QACrE;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO,4BAA4B,IAAI,IAAI,GAAG,IAAI;AAAA,EACpD;AAGA,MAAI,YAAY,UAAU;AAExB,QAAI,CAAC,oBAAoB,UAAU,QAAQ,GAAG;AAC5C,YAAM,IAAI;AAAA,QACR,GAAG,SAAS,IAAI,KAAK,GAAG,MAAM,IAAI;AAAA,QAClC,GAAG,SAAS,IAAI,KAAK,GAAG,MAAM,IAAI;AAAA,MACpC;AAAA,IACF;AAGA,QAAI,kBAAkB;AAOtB,QAAI,CAAC,iBAAiB;AACpB,UAAI,SAAS,WAAW,YAAY,SAAS,WAAW,UAAU;AAChE,0BAAkB;AAAA,MACpB,OAAO;AAGL,YAAI,SAAS,WAAW,QAAQ,SAAS,WAAW,MAAM;AACxD,4BAAkB;AAAA,QACpB,OAAO;AAEL,gBAAM,kBACJ,SAAS,WAAW,QACnB,SAAS,WAAW,eACnB,SAAS,kBACT,QAAQ,SAAS;AACrB,gBAAM,kBACJ,SAAS,WAAW,QACnB,SAAS,WAAW,eACnB,SAAS,kBACT,QAAQ,SAAS;AACrB,4BACE,mBAAmB,kBAAkB,OAAO;AAAA,QAChD;AAAA,MACF;AAAA,IACF;AAEA,WAAO,mBAAmB,IAAI,IAAI,UAAU,UAAU,iBAAiB;AAAA,MACrE;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAGA,QAAM,IAAI;AAAA,IACR,GAAG,MAAM;AAAA,IACT,GAAG,MAAM;AAAA,EACX;AACF;AAKA,SAAS,mBACP,IACA,IACA,UACA,UACA,QACA,YAC0B;AAE1B,QAAM,UAAU,UAAU,UAAU,MAAM;AAC1C,QAAM,UAAU,UAAU,UAAU,MAAM;AAG1C,MAAI;AACJ,MAAI,GAAG,SAAS,WAAW,GAAG,SAAS,SAAS;AAC9C,UAAM,OAAO,gBAAgB,GAAG,KAAqC;AACrE,UAAM,OAAO,gBAAgB,GAAG,KAAqC;AACrE,gBAAY,OAAO,UAAU,OAAO;AAAA,EACtC,OAAO;AAEL,UAAM,OAAO,gBAAgB,EAAE;AAC/B,UAAM,OAAO,gBAAgB,EAAE;AAC/B,gBAAY,OAAO,UAAU,OAAO;AAAA,EACtC;AAGA,QAAM,EAAE,MAAM,UAAU,OAAO,UAAU,IAAI;AAAA,IAC3C;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA;AAAA,EACF;AAGA,QAAM,iBAAiB,kBAAkB,WAAW,QAAQ;AAG5D,MAAI,GAAG,SAAS,WAAW,GAAG,SAAS,SAAS;AAC9C,UAAM,KACJ,GAAG,SAAS,UACR,KACA,EAAE,MAAM,SAAkB,KAAK,GAAG,OAAO,KAAK,GAAG,MAAM;AAC7D,UAAM,KACJ,GAAG,SAAS,UACR,KACA,EAAE,MAAM,SAAkB,KAAK,GAAG,OAAO,KAAK,GAAG,MAAM;AAE7D,UAAM,YACJ,gBAAgB,GAAG,GAAmC,IAAI,UAC1D,gBAAgB,GAAG,GAAmC,IAAI;AAC5D,UAAM,YACJ,gBAAgB,GAAG,GAAmC,IAAI,UAC1D,gBAAgB,GAAG,GAAmC,IAAI;AAE5D,UAAM,aAAa,UAAU,UAAU,MAAM;AAC7C,UAAM,WAAW,YAAY;AAC7B,UAAM,WAAW,YAAY;AAE7B,WAAO;AAAA,MACL,UAAU;AAAA,QACR,MAAM;AAAA,QACN,KAAK,kBAAkB,UAAU,QAAQ;AAAA,QACzC,KAAK,kBAAkB,UAAU,QAAQ;AAAA,MAC3C;AAAA,MACA,MAAM,EAAE,MAAM,SAAS,KAAK;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU,EAAE,MAAM,SAAS,OAAO,eAAe;AAAA,IACjD,MAAM,EAAE,MAAM,SAAS,KAAK;AAAA,EAC9B;AACF;AAqBO,SAAS,wBACd,UACA,QAC8D;AAC9D,QAAM,UAAU;AAAA,IACd,OAAO,SAAS,SAAS,WAAW,SAAS,OAAO,SAAS,MAAM;AAAA,EACrE;AAGA,MAAI,QAAQ,SAAS,WAAW,EAAE,YAAY,UAAU;AACtD,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,gBAAgB,SAAS,QAAQ;AAClD,MAAI,OAAO,aAAa,UAAU;AAChC,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,UAAU,SAAS,MAAM;AACxC,QAAM,cAAc,WAAW;AAC/B,QAAM,EAAE,MAAM,UAAU,OAAO,UAAU,IAAI;AAAA,IAC3C;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA,CAAC,OAAO;AAAA,EACV;AAGA,QAAM,iBAAiB,kBAAkB,WAAW,QAAQ;AAG5D,MAAI,SAAS,SAAS,SAAS,SAAS;AACtC,UAAM,aAAa,UAAU,UAAU,MAAM;AAE7C,UAAM,WACH,gBAAgB,SAAS,SAAS,GAAG,IAAI,SAAU;AACtD,UAAM,WACH,gBAAgB,SAAS,SAAS,GAAG,IAAI,SAAU;AAEtD,WAAO;AAAA,MACL,UAAU;AAAA,QACR,MAAM;AAAA,QACN,KAAK,kBAAkB,UAAU,QAAQ;AAAA,QACzC,KAAK,kBAAkB,UAAU,QAAQ;AAAA,MAC3C;AAAA,MACA,MAAM,EAAE,MAAM,SAAS,KAAK;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU,EAAE,MAAM,SAAS,OAAO,eAAe;AAAA,IACjD,MAAM,EAAE,MAAM,SAAS,KAAK;AAAA,EAC9B;AACF;AAEO,SAAS,YACd,UAGiE;AACjE,MAAI,WAAW,QAAQ;AACrB,WAAO,SAAS,OACZ,EAAE,GAAG,UAAU,MAAM,SAAS,KAAK,KAAK,IACvC;AAAA,WACE,UAAU,QAAQ,GAAG;AAC5B,WAAO;AAAA,MACL,IAAI,SAAS,GAAG,IAAI,WAAW;AAAA,IACjC;AAAA,EACF,OAAO;AACL,WAAO;AAAA,MACL,KAAK,SAAS,IAAI,IAAI,WAAW;AAAA,IACnC;AAAA,EACF;AACF;AAaO,SAAS,eACd,GACuE;AACvE,MAAI,WAAW,CAAC,GAAG;AACjB,WAAO,EAAE,OACL,EAAE,GAAG,GAAG,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,IAC9B;AAAA,EACP,WAAW,UAAU,CAAC,GAAG;AACvB,WAAO,EAAE,IAAI,EAAE,GAAG,IAAI,cAAc,EAAE;AAAA,EACxC,OAAO;AACL,WAAO,EAAE,KAAK,EAAE,IAAI,IAAI,cAAc,EAAE;AAAA,EAC1C;AACF;AAEO,SAAS,oBACd,GAC0B;AAC1B,QAAM,SAAmC;AAAA,IACvC,UAAU,EAAE;AAAA,EACd;AACA,MAAI,CAAC,SAAS,EAAE,IAAI,GAAG;AACrB,WAAO,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK;AAAA,EACpC;AACA,SAAO;AACT;AAIO,IAAM,wBAAwB,CACnC,WAOK;AACL,MAAI,UAAU,MAAM,GAAG;AAIrB,UAAM,UAAU,OAAO;AACvB,UAAM,gBAAgB,QAAQ;AAAA,MAC5B,CAACC,OAAoD,WAAWA,EAAC;AAAA,IACnE;AAEA,QAAI,eAAe;AAEjB,YAAM,aAAsC,CAAC;AAI7C,YAAM,uBACJ,cACA;AACF,iBAAW,SAAS,sBAAsB;AACxC,mBAAW,KAAK;AAAA,UACd,UAAU,MAAM;AAAA,UAChB,GAAI,MAAM,QAAQ,EAAE,MAAM,MAAM,KAAK;AAAA,QACvC,CAAC;AAAA,MACH;AAGA,YAAM,kBAA2C,QAC9C,OAAO,CAACA,OAAkC,WAAWA,EAAC,CAAC,EACvD,IAAI,CAACA,QAAO,EAAE,UAAUA,GAAE,UAAU,MAAMA,GAAE,KAAK,EAAE;AAEtD,UAAI,gBAAgB,SAAS,GAAG;AAC9B,eAAO;AAAA,UACL;AAAA,YACE,KAAK;AAAA,YACL,aAAa;AAAA,UACf;AAAA,QACF;AAAA,MACF,OAAO;AAEL,eAAO;AAAA,MACT;AAAA,IACF;AAGA,UAAM,gBAAgB,QAAQ;AAAA,MAAO,CAACA,OACpC,WAAWA,EAAC;AAAA,IACd;AAEA,QAAI,cAAc,SAAS,GAAG;AAC5B,YAAM,SAAgC;AAAA,QACpC,UAAU,cAAc,CAAC,EAAG;AAAA,QAC5B,MAAM,cAAc,CAAC,EAAG;AAAA,MAC1B;AACA,UAAI,cAAc,SAAS,GAAG;AAC5B,eAAO,cAAc,cAAc,MAAM,CAAC;AAAA,MAC5C;AACA,aAAO,CAAC,MAAM;AAAA,IAChB,OAEK;AACH,YAAM,QAAQ,QAAQ,CAAC;AACvB,aAAO,CAAC,EAAE,UAAU,MAAM,UAAU,MAAM,MAAM,KAAK,CAAC;AAAA,IACxD;AAAA,EACF,WAAW,WAAW,MAAM,GAAG;AAE7B,UAAM,aAAsC,CAAC;AAC7C,UAAM,kBAA2C,CAAC;AAClD,eAAW,SAAS,OAAO,KAAK;AAG9B,UAAI,UAAU,KAAK,GAAG;AAEpB,cAAM,YAAY,MAAM;AACxB,mBAAW,KAAK;AAAA,UACd,UAAU,UAAU,CAAC,EAAG;AAAA,UACxB,GAAI,UAAU,CAAC,EAAG,QAAQ,EAAE,MAAM,UAAU,CAAC,EAAG,KAAK;AAAA,QACvD,CAAC;AAED,wBAAgB,KAAK,GAAG,UAAU,MAAM,CAAC,CAAC;AAAA,MAC5C,WAAW,WAAW,KAAK,GAAG;AAE5B,mBAAW,KAAK;AAAA,UACd,UAAU,MAAM;AAAA,UAChB,GAAI,MAAM,QAAQ,EAAE,MAAM,MAAM,KAAK;AAAA,QACvC,CAAC;AAAA,MACH;AAAA,IACF;AAKA,QAAI,gBAAgB,WAAW,GAAG;AAEhC,aAAO;AAAA,IACT;AAEA,UAAM,SAGF;AAAA,MACF,KAAK;AAAA,MACL,aAAa;AAAA,IACf;AAEA,WAAO,CAAC,MAAM;AAAA,EAChB,OAAO;AAEL,WAAO;AAAA,MACL,EAAE,UAAU,OAAO,UAAU,GAAI,OAAO,QAAQ,EAAE,MAAM,OAAO,KAAK,EAAG;AAAA,IACzE;AAAA,EACF;AACF;AAYO,SAAS,cACd,GACA,QAC0B;AAE1B,MAAI,CAAC,EAAE,MAAM,MAAM;AACjB,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,YAAY,EAAE,KAAK,IAAI;AAGvC,MAAI,QAAQ,SAAS,SAAS;AAC5B,WAAO;AAAA,EACT;AAGA,MAAI,EAAE,SAAS,SAAS,WAAW,EAAE,SAAS,MAAM,SAAS,QAAQ;AACnE,WAAO;AAAA,EACT;AAGA,QAAM,WAAW,gBAAgB,EAAE,QAAQ;AAG3C,QAAM,kBACJ,WACC,CAAC,UAAU,IAAI,EAAE,SAAS,QAAQ,MAAM,IACpC,QAAQ,SACT;AAGN,QAAM,SAAS,UAAU,SAAS,eAAe;AACjD,QAAM,cAAc,WAAW;AAG/B,QAAM,EAAE,MAAM,UAAU,OAAO,UAAU,IAAI;AAAA,IAC3C;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA,CAAC,OAAO;AAAA,EACV;AAGA,QAAM,wBAAwB,cAAc,EAAE,KAAK,IAAI,GAAG;AAG1D,MAAI,SAAS,SAAS,uBAAuB;AAC3C,WAAO;AAAA,EACT;AAGA,QAAM,iBAAiB,kBAAkB,WAAW,QAAQ;AAG5D,MAAI,EAAE,SAAS,SAAS,SAAS;AAC/B,UAAM,aAAa,UAAU,UAAU,eAAe;AACtD,UAAM,WAAY,gBAAgB,EAAE,SAAS,GAAG,IAAI,SAAU;AAC9D,UAAM,WAAY,gBAAgB,EAAE,SAAS,GAAG,IAAI,SAAU;AAE9D,WAAO;AAAA,MACL,UAAU;AAAA,QACR,MAAM;AAAA,QACN,KAAK,kBAAkB,UAAU,QAAQ;AAAA,QACzC,KAAK,kBAAkB,UAAU,QAAQ;AAAA,MAC3C;AAAA,MACA,MAAM,EAAE,MAAM,SAAS,KAAK;AAAA,IAC9B;AAAA,EACF;AAGA,SAAO;AAAA,IACL,UAAU;AAAA,MACR,MAAM;AAAA,MACN,OAAO;AAAA,IACT;AAAA,IACA,MAAM,EAAE,MAAM,SAAS,KAAK;AAAA,EAC9B;AACF;;;AClpBO,SAAS,iBACd,SACA,WACY;AACZ,MAAI,UAAU,SAAS,GAAG;AACxB,YAAQ,QAAQ,KAAK,EAAE,MAAM,QAAQ,OAAO,CAAC,GAAG,SAAS,EAAE,CAAC;AAC5D,WAAO,CAAC;AAAA,EACV;AACA,SAAO;AACT;AAQO,SAAS,kBACd,SACA,OACS;AACT,MAAI,MAAM,SAAS,GAAG;AACpB,YAAQ,QAAQ,KAAK,EAAE,MAAM,QAAQ,OAAO,CAAC,GAAG,KAAK,EAAE,CAAC;AACxD,UAAM,SAAS;AACf,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAWO,SAAS,wBACd,aACA,eACA,aACQ;AACR,QAAM,EAAE,KAAK,IAAI;AAEjB,MAAI,aAAa;AACf,UAAM,YAAY,YAAY;AAAA,MAC5B,CAACC,OAAMA,GAAE,KAAK,YAAY,MAAM,KAAK,YAAY;AAAA,IACnD;AAEA,QAAI,cAAc,IAAI;AACpB,YAAM,IAAI;AAAA,QACR,0BAA0B,IAAI;AAAA,MAChC;AAAA,IACF;AAGA,UAAM,qBAAqB,YAAY,SAAS;AAIhD,QAAI,CAAC,cAAc,OAAO;AACxB,UACE,MAAM,QAAQ,mBAAmB,KAAK,KACtC,mBAAmB,MAAM,SAAS,GAClC;AACA,cAAM,IAAI;AAAA,UACR;AAAA,UACA,mBAAmB;AAAA,UACnB,mBAAmB,MAAM,CAAC;AAAA,QAC5B;AAAA,MACF;AAAA,IACF,OAAO;AACL,iBAAW,QAAQ,cAAc,OAAO;AAEtC,YACE,mBAAmB,UAAU,UAC7B,CAAC,mBAAmB,MAAM,SAAS,IAAI,GACvC;AACA,gBAAM,IAAI;AAAA,YACR;AAAA,YACA,mBAAmB;AAAA,YACnB;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAGA,SAAO,YAAY,KAAK,aAAa,IAAI;AAC3C;AAEO,SAAS,sBACd,UACA,aACA,aACQ;AACR,QAAM,EAAE,MAAM,SAAS,IAAI;AAE3B,MAAI,aAAa;AACf,UAAM,QAAQ,SAAS;AAAA,MACrB,CAACA,OAAMA,GAAE,KAAK,YAAY,MAAM,KAAK,YAAY;AAAA,IACnD;AAEA,QAAI,UAAU,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,wBAAwB,IAAI;AAAA,MAC9B;AAAA,IACF;AAEA,UAAM,mBAAmB,SAAS,KAAK;AAIvC,QAAI,CAAC,YAAY,OAAO;AACtB,UACE,MAAM,QAAQ,iBAAiB,KAAK,KACpC,iBAAiB,MAAM,SAAS,GAChC;AACA,cAAM,IAAI;AAAA,UACR;AAAA,UACA,iBAAiB;AAAA,UACjB,iBAAiB,MAAM,CAAC;AAAA,QAC1B;AAAA,MACF;AAAA,IACF,OAAO;AACL,iBAAW,QAAQ,YAAY,OAAO;AAEpC,YACE,iBAAiB,UAAU,UAC3B,CAAC,iBAAiB,MAAM,SAAS,IAAI,GACrC;AACA,gBAAM,IAAI;AAAA,YACR;AAAA,YACA,iBAAiB;AAAA,YACjB;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,aAAa,QAAW;AAC1B,UAAI,CAAC,iBAAiB,UAAU;AAC9B,yBAAiB,WAAW;AAAA,MAC9B,OAAO;AACL,YAAI;AACF,2BAAiB,WAAW;AAAA,YAC1B,iBAAiB;AAAA,YACjB;AAAA,UACF;AAAA,QACF,SAASC,IAAG;AAEV,cAAIA,cAAa,yBAAyB;AACxC,mBAAO,SAAS,KAAK,WAAW,IAAI;AAAA,UACtC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,SAAO,SAAS,KAAK,WAAW,IAAI;AACtC;AAGO,IAAM,kBAAkB,CAC7B,cAC6C;AAC7C,MAAI,CAAC,gBAAgB,KAAK,SAAS,GAAG;AACpC,WAAO,EAAE,MAAM,QAAQ,MAAM,UAAU;AAAA,EACzC;AAGA,QAAM,IAAI,UAAU,KAAK,EAAE,QAAQ,KAAK,GAAG;AAG3C,MAAI,EAAE,SAAS,GAAG,GAAG;AACnB,UAAM,QAAQ,EAAE,MAAM,GAAG;AAEzB,UAAM,MAAM,OAAO,MAAM,CAAC,CAAC;AAC3B,UAAM,MAAM,OAAO,MAAM,CAAC,CAAC;AAE3B,WAAO,EAAE,MAAM,YAAY,KAAK,IAAI;AAAA,EACtC;AAGA,SAAO,EAAE,MAAM,WAAW,SAAS,OAAO,CAAC,EAAE;AAC/C;AAEO,SAAS,uBAAuB,UAAsC;AAC3E,MAAI,SAAS,SAAS,SAAS;AAC7B,WAAO,oBAAoB,QAAQ;AAAA,EACrC,OAAO;AACL,WAAO,GAAG,oBAAoB,EAAE,MAAM,SAAS,OAAO,SAAS,IAAI,CAAC,CAAC,IAAI,oBAAoB,EAAE,MAAM,SAAS,OAAO,SAAS,IAAI,CAAC,CAAC;AAAA,EACtI;AACF;AAEA,SAAS,oBAAoB,UAA8B;AACzD,MAAI,SAAS,MAAM,SAAS;AAC1B,WAAO,GAAG,SAAS,MAAM,GAAG,IAAI,SAAS,MAAM,GAAG;AAAA,WAC3C,SAAS,MAAM,SAAS;AAC/B,WAAO,OAAO,SAAS,MAAM,OAAO;AAAA,MACjC,QAAO,SAAS,MAAM;AAC7B;AAGO,SAAS,mBAAmB,WAAuC;AACxE,QAAM,YAAY,OAAO,SAAS,EAAE,KAAK;AAEzC,MAAI,WAAW,KAAK,SAAS,GAAG;AAC9B,UAAM,cAAc,UAAU,MAAM,GAAG;AAEvC,UAAM,MAAM,gBAAgB,YAAY,CAAC,EAAG,KAAK,CAAC;AAGlD,UAAM,MAAM,gBAAgB,YAAY,CAAC,EAAG,KAAK,CAAC;AAGlD,WAAO,EAAE,MAAM,SAAS,KAAK,IAAI;AAAA,EACnC;AAEA,SAAO,EAAE,MAAM,SAAS,OAAO,gBAAgB,SAAS,EAAE;AAC5D;AAEO,SAAS,mBAAmB,SAAiB,SAAiB;AACnE,QAAM,WAAW,QAAQ;AAAA,IACvB,IAAI,OAAO,IAAI,OAAO,gCAAgC,GAAG;AAAA,EAC3D;AACA,SAAO,WACH,SAAS,CAAC,GAAG,KAAK,EAAE,QAAQ,gBAAgB,GAAG,IAC/C;AACN;AAEO,SAAS,oBACd,SACA,SAC8B;AAC9B,QAAM,WAAW,QAAQ,MAAM,sBAAsB,OAAO,CAAC;AAC7D,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI,MAAM,OAAO,SAAS,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG;AACtC,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AACA,SAAO,CAAC,OAAO,SAAS,CAAC,GAAG,KAAK,CAAC,GAAG,SAAS,CAAC,EAAG,KAAK,CAAC;AAC1D;AAEO,SAAS,iBAAiB,SAAiB,SAAiB;AAEjE,QAAM,YAAY,QAAQ;AAAA,IACxB,IAAI;AAAA,MACF,IAAI,OAAO;AAAA,MACX;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,UAAW,QAAO;AAGvB,MAAI,UAAU,CAAC,MAAM,QAAW;AAE9B,WAAO,UAAU,CAAC,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC;AAAA,EACxD,WAAW,UAAU,CAAC,GAAG;AAKvB,WAAO,UAAU,CAAC,EACf,MAAM,IAAI,EACV,OAAO,CAAC,SAAS,KAAK,KAAK,MAAM,EAAE,EACnC,IAAI,CAAC,SAAS,KAAK,QAAQ,YAAY,EAAE,EAAE,KAAK,CAAC;AAAA,EACtD;AACF;AAEO,SAAS,gBAAgB,SAAkC;AAChE,QAAM,WAAqB,CAAC;AAC5B,MAAI,WAA+B;AAGnC,QAAM,kBAAkB,QAAQ,MAAM,aAAa,IAAI,CAAC;AACxD,MAAI,CAAC,iBAAiB;AACpB,WAAO,EAAE,SAAS;AAAA,EACpB;AAGA,aAAW,WAAW;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAyBM;AACJ,UAAM,kBAAkB,mBAAmB,iBAAiB,OAAO;AACnE,QAAI,gBAAiB,UAAS,OAAO,IAAI;AAAA,EAC3C;AAGA,MAAI;AACJ,QAAM,gBAAgB,mBAAmB,iBAAiB,aAAa;AACvE,MAAI,eAAe;AACjB,aAAS,aAAa,IAAI;AAC1B,UAAM,gBAAoD;AAAA,MACxD,QAAQ;AAAA,MACR,IAAI;AAAA,MACJ,IAAI;AAAA,MACJ,IAAI;AAAA,IACN;AACA,iBAAa,cAAc,cAAc,YAAY,CAAC;AAAA,EACxD;AAGA,aAAW,WAAW,CAAC,UAAU,SAAS,UAAU,GAG9C;AACJ,UAAM,mBAAmB,oBAAoB,iBAAiB,OAAO;AACrE,QAAI,oBAAoB,iBAAiB,CAAC,GAAG;AAC3C,eAAS,OAAO,IAAI,iBAAiB,CAAC;AACtC,iBAAW,iBAAiB,CAAC;AAAA,IAC/B;AAAA,EACF;AAGA,aAAW,WAAW,CAAC,QAAQ,UAAU,UAAU,GAG7C;AACJ,UAAM,gBAAgB,iBAAiB,iBAAiB,OAAO;AAC/D,QAAI,cAAe,UAAS,OAAO,IAAI;AAAA,EACzC;AAEA,SAAO,EAAE,UAAU,UAAU,WAAW;AAC1C;AAEO,SAAS,wBAAwB,KAAsB;AAC5D,SAAO,QAAQ,KAAK,GAAG;AACzB;AAEO,SAAS,YAAe,IAAY,IAAoB;AAC7D,QAAM,SAAS,IAAI,IAAI,EAAE;AACzB,aAAW,QAAQ,IAAI;AACrB,WAAO,IAAI,IAAI;AAAA,EACjB;AACA,SAAO;AACT;AAQO,SAAS,wBACd,cACe;AACf,MAAI,CAAC,gBAAgB,aAAa,WAAW,EAAG,QAAO;AACvD,SAAO,aACJ,IAAI,CAACC,OAAMA,GAAE,KAAK,EAClB,KAAK,CAACA,IAAG,MAAMA,KAAI,CAAC,EACpB,KAAK,GAAG;AACb;;;AVtYO,IAAM,iBAAN,MAAqB;AAAA,EAG1B,YAAY,aAAsB;AAFlC,wBAAO,YAA4B,CAAC;AAGlC,QAAI,YAAa,MAAK,MAAM,WAAW;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,MAAM,aAAsC;AACjD,UAAM,aAAa,iBAAAC,QAAK,MAAM,WAAW;AAGzC,SAAK,WAAW,CAAC;AAEjB,QAAI,CAAC,KAAK,mBAAmB,UAAU,GAAG;AACxC,YAAM,IAAI,4BAA4B;AAAA,IACxC;AAEA,eAAW,CAAC,gBAAgB,cAAc,KAAK,OAAO,QAAQ,UAAU,GAAG;AACzE,YAAM,kBAAkB;AACxB,YAAM,UAAU,gBAAgB;AAEhC,iBAAW,CAAC,KAAK,WAAW,KAAK,OAAO,QAAQ,eAAe,GAAG;AAChE,YAAI,QAAQ,WAAW;AACrB;AAAA,QACF;AAEA,cAAM,YAAY;AAClB,cAAM,EAAE,MAAM,MAAM,OAAO,GAAG,KAAK,IACjC;AAGF,cAAM,cAAc,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;AACtD,cAAM,QAAuB,YAAY,IAAI,CAAC,YAAY;AACxD,gBAAM,iBAAiB,QAAQ,MAAM,GAAG;AACxC,gBAAM,aAAa;AAAA,YACjB,eAAe,CAAC;AAAA,UAClB;AACA,gBAAM,cAA2B,EAAE,MAAM,WAAW;AACpD,cAAI,eAAe,SAAS,GAAG;AAC7B,wBAAY,OAAO,eAAe,CAAC;AAAA,UACrC;AACA,iBAAO;AAAA,QACT,CAAC;AAED,cAAM,gBAA+B;AAAA,UACnC,IAAI;AAAA,UACJ,aAAa;AAAA,UACb;AAAA,UACA;AAAA,UACA;AAAA,UACA,GAAG;AAAA,QACL;AACA,YAAI,SAAS;AACX,wBAAc,oBAAoB;AAAA,QACpC;AAEA,aAAK,SAAS,KAAK,aAAa;AAAA,MAClC;AAAA,IACF;AAEA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,YAAoB;AACzB,UAAM,UAAqC,CAAC;AAE5C,eAAW,WAAW,KAAK,UAAU;AACnC,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG;AAAA,MACL,IAAI;AACJ,UAAI,CAAC,QAAQ,cAAc,GAAG;AAC5B,gBAAQ,cAAc,IAAI,CAAC;AAAA,MAC7B;AACA,UAAI,qBAAqB,CAAC,QAAQ,cAAc,EAAE,SAAS;AACzD,gBAAQ,cAAc,EAAE,UAAU;AAAA,MACpC;AAGA,YAAM,cAAc,MAAM;AAAA,QAAI,CAAC,MAC7B,EAAE,OACE,GAAG,uBAAuB,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,KAC3C,uBAAuB,EAAE,IAAI;AAAA,MACnC;AAEA,cAAQ,cAAc,EAAE,EAAE,IAAI;AAAA,QAC5B,GAAG;AAAA,QACH,MAAM;AAAA;AAAA,QAEN,MAAM,YAAY,WAAW,IAAI,YAAY,CAAC,IAAK;AAAA,MACrD;AAAA,IACF;AAEA,WAAO,iBAAAA,QAAK,UAAU,OAAO;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,IAAI,eAAoC;AAC7C,SAAK,SAAS,KAAK,aAAa;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,OAAO,WAAyB;AACrC,SAAK,WAAW,KAAK,SAAS,OAAO,CAAC,YAAY,QAAQ,OAAO,SAAS;AAAA,EAC5E;AAAA,EAEQ,mBAAmB,SAA6B;AACtD,eAAW,eAAe,OAAO,OAAO,OAAO,GAAG;AAChD,UAAI,OAAO,gBAAgB,YAAY,gBAAgB,MAAM;AAC3D,eAAO;AAAA,MACT;AAEA,iBAAW,CAAC,IAAI,GAAG,KAAK,OAAO,QAAQ,WAAW,GAAG;AACnD,YAAI,OAAO,WAAW;AACpB,cAAI,CAAC,MAAM,QAAQ,GAAG,GAAG;AACvB,mBAAO;AAAA,UACT;AAAA,QACF,OAAO;AACL,cAAI,CAAC,wBAAwB,EAAE,GAAG;AAChC,mBAAO;AAAA,UACT;AACA,cAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAC3C,mBAAO;AAAA,UACT;AAEA,gBAAM,SAAS;AACf,gBAAM,OAAO,OAAO,KAAK,MAAM;AAE/B,gBAAM,gBAAgB,CAAC,QAAQ,QAAQ,OAAO;AAE9C,cAAI,cAAc,KAAK,CAAC,QAAQ,CAAC,KAAK,SAAS,GAAG,CAAC,GAAG;AACpD,mBAAO;AAAA,UACT;AAEA,gBAAM,iBAAiB,OAAO,OAAO,SAAS;AAE9C,gBAAM,UACJ,OAAO,OAAO,SAAS,YACtB,MAAM,QAAQ,OAAO,IAAI,KACxB,OAAO,KAAK,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ;AAClD,gBAAM,WAAW,OAAO,OAAO,UAAU;AAEzC,cAAI,EAAE,kBAAkB,WAAW,WAAW;AAC5C,mBAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;;;AW5MO,IAAM,UAAN,MAAc;AAAA;AAAA;AAAA;AAAA;AAAA,EAanB,YAAY,OAAe,IAAI;AAR/B;AAAA;AAAA;AAAA;AAAA;AAEA;AAAA,mCAA2B,CAAC;AAO1B,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAmB;AACjB,WAAO,KAAK,SAAS,MAAM,KAAK,QAAQ,WAAW;AAAA,EACrD;AACF;;;ACrBA,IAAAC,cAAgB;;;ACVT,SAAS,+BACd,MACA,UACA;AACA,QAAM,sBAAsB;AAAA,IAC1B,GAAG;AAAA,IACH,MAAM,YAAY,SAAS,MAAM,IAAI;AAAA,EACvC;AACA,SAAO,KAAK;AAAA,IAAK,CAAC,MAChB,EAAE,KAAK,CAAC,OAAO,kBAAkB,GAAG,MAAM,oBAAoB,IAAI,CAAC;AAAA,EACrE;AACF;AAEO,SAAS,iCACd,MACA,UACiC;AACjC,QAAM,sBAAsB;AAAA,IAC1B,GAAG;AAAA,IACH,MAAM,YAAY,SAAS,MAAM,IAAI;AAAA,EACvC;AACA,SAAO,KAAK;AAAA,IACV,CAAC,MACC,EAAE,KAAK,SAAS,oBAAoB,KAAK,QACxC,EAAE,KAAK,SAAS,oBAAoB,KAAK,QACxC,EAAE,KAAK,SAAS;AAAA,EACtB;AACF;;;AC/BA,IAAM,kBAAkB,CAAI,MAAY;AACtC,MAAI,MAAM,QAAQ,OAAO,MAAM,UAAU;AACvC,WAAO;AAAA,EACT;AACA,MAAI,aAAa,KAAK;AACpB,WAAO,IAAI;AAAA,MACT,MAAM,KAAK,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,GAAG,MAAM;AAAA,QACxC,gBAAgB,CAAC;AAAA,QACjB,gBAAgB,GAAG;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,EACF;AACA,MAAI,aAAa,KAAK;AACpB,WAAO,IAAI,IAAI,MAAM,KAAK,CAAC,EAAE,IAAI,CAAC,QAAiB,gBAAgB,GAAG,CAAC,CAAC;AAAA,EAC1E;AACA,MAAI,aAAa,MAAM;AACrB,WAAO,IAAI,KAAK,EAAE,QAAQ,CAAC;AAAA,EAC7B;AACA,MAAI,MAAM,QAAQ,CAAC,GAAG;AACpB,WAAO,EAAE,IAAI,CAAC,SAAkB,gBAAgB,IAAI,CAAC;AAAA,EACvD;AACA,QAAM,SAAS,CAAC;AAChB,aAAW,OAAO,OAAO,KAAK,CAAC,GAAG;AAChC,WAAO,GAAG,IAAI,gBAAiB,EAA8B,GAAG,CAAC;AAAA,EACnE;AACA,SAAO;AACT;AAEO,IAAM,YAAY,CAAI,MAC3B,OAAO,oBAAoB,aACvB,gBAAgB,CAAC,IACjB,gBAAgB,CAAC;;;AFIhB,SAAS,2BACX,YAIsB;AACzB,QAAM,iBAAiB,UAAU,UAAU;AAE3C,QAAM,WACJ,eAAe,OAAO,SAAS,EAC/B,OAAO,CAAC,MAAM,EAAE,GAAG,SAAS,CAAC;AAE/B,QAAM,YAAqC,CAAC;AAC5C,QAAM,mBAAmB,CAAC,QAA+C;AAAA,IACvE,GAAG;AAAA,IACH,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO;AAAA,MACpB,GAAG;AAAA,MACH,MAAM,YAAY,EAAE,MAAM,MAAM,EAAE,MAAM,gBAAgB;AAAA,IAC1D,EAAE;AAAA,EACJ;AAEA,WAAS,sBACP,OACA,cACA;AACA,WAAO,MAAM,UAAU,CAAC,MAAM;AAC5B,YAAM,YAAY,EAAE,IAAI,CAAC,MAAM,YAAY,EAAE,MAAM,IAAI,CAAC;AACxD,aAAO,aAAa;AAAA,QAClB,CAAC,MAAM,KAAK,UAAU,KAAK,CAAC,OAAO,kBAAkB,IAAI,CAAC,CAAC;AAAA,MAC7D;AAAA,IACF,CAAC;AAAA,EACH;AAEA,WAAS,qBACP,OACA,KACA,IACA;AACA,QAAI;AACJ,UAAM,iBAAiB,MAAM,GAAG,EAAG,OAAO,CAAC,KAAK,MAAM;AACpD,YAAM,cAAmC;AAAA,QACvC,GAAG;AAAA,QACH,MAAM,YAAY,EAAE,MAAM,MAAM,EAAE,MAAM,gBAAgB;AAAA,MAC1D;AAEA,YAAM,iBAAiB,GAAG,GAAG;AAAA,QAC3B,CAAC,MAAM,WAAW,CAAC,KAAK,kBAAkB,EAAE,MAAM,YAAY,IAAI;AAAA,MACpE;AACA,UAAI,gBAAgB;AAClB,YAAI,KAAK,WAAW;AAGpB,YAAI,CAAC,WAAW;AACd,sBAAY,aAAa,aAAa,cAAc;AAAA,QACtD;AAAA,MACF;AACA,aAAO;AAAA,IACT,GAAG,CAAC,CAA0B;AAE9B,eAAW,QAAQ,GAAG,IAAI;AACxB,UAAI,eAAe,KAAK,CAAC,MAAM,kBAAkB,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG;AACpE;AAAA,MACF,OAAO;AACL,cAAM,iBAAiB,sBAAsB,KAAK,UAAU,SAAU;AACtE,cAAM,GAAG,EAAG,KAAK,EAAE,GAAG,MAAM,UAAU,eAAe,CAAC;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AAEA,aAAW,WAAW,UAAU;AAC9B,UAAM,kBAAkB,iBAAiB,OAAO;AAChD,UAAMC,SAAQ,gBAAgB,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI;AAClD,UAAM,YAAY,sBAAsB,WAAWA,MAAK;AACxD,QAAI,cAAc,IAAI;AACpB,gBAAU,KAAK,gBAAgB,EAAE;AAAA,IACnC,OAAO;AACL,2BAAqB,WAAW,WAAW,eAAe;AAAA,IAC5D;AAAA,EACF;AAEA,SAAO;AACT;AAOO,SAAS,aAAa,MAA6B;AACxD,MAAI,CAAC,QAAQ,KAAK,UAAU,EAAG,QAAO;AACtC,QAAM,eAAsC,CAAC;AAC7C,QAAM,kBAAyC,CAAC;AAChD,aAAW,KAAK,MAAM;AACpB,QAAI,EAAE,KAAK,oBAAoB,EAAE,KAAK,WAAW,QAAQ;AACvD,mBAAa,KAAK,CAAC;AAAA,IACrB,OAAO;AACL,sBAAgB,KAAK,CAAC;AAAA,IACxB;AAAA,EACF;AAEA,SAAO,aACJ,KAAK,CAACC,IAAG,MAAM;AACd,UAAM,UAAUA,GAAE,KAAK,mBAAmB,QAAQ;AAClD,UAAM,UAAU,EAAE,KAAK,mBAAmB,QAAQ;AAClD,YAAQ,UAAUA,GAAE,KAAK,MAAM,cAAc,UAAU,EAAE,KAAK,MAAM,IAAI;AAAA,EAC1E,CAAC,EACA,OAAO,eAAe;AAC3B;AAEO,SAAS,2BACd,UACA,YAI4B;AAC5B,WAAS,iBAAiB,eAAyC;AAEjE,UAAM,iBAAiB;AAAA,MACrB,+BAA+B,UAAU,aAAa;AAAA,IACxD;AACA,QAAI,CAAC,eAAgB,QAAO;AAE5B,UAAM,sBAAsB;AAAA,MAC1B;AAAA,MACA;AAAA,IACF;AAEA,UAAM,0BAA+C;AAAA,MACnD,GAAG;AAAA,MACH,MAAM,YAAY,cAAc,MAAM,IAAI;AAAA,IAC5C;AAEA,QAAI,oBAAoB,KAAK,kBAAkB;AAC7C,YAAM,iBAA2C;AAAA,QAC/C,UAAU,cAAc;AAAA,MAC1B;AAEA,UAAI,CAAC,SAAS,wBAAwB,IAAI,GAAG;AAC3C,uBAAe,OAAO,EAAE,MAAM,wBAAwB,KAAK,KAAK;AAAA,MAClE;AACA,aAAO;AAAA,IACT,OAAO;AAEL,UAAI;AACJ,YAAM,qBAAqB,CAAC,GAAG,cAAc;AAC7C,aAAO,kBAAkB,IAAI;AAC3B,wBAAgB,mBAAmB;AAAA,UACjC,CAAC,OAAO,GAAG,MAAM;AAAA,QACnB;AAEA,YAAI,kBAAkB,IAAI;AACxB,gBAAMC,aAAY;AAAA,YAChB,mBAAmB,aAAa;AAAA,YAChC;AAAA,UACF;AACA,gBAAM,6BAA6B;AAAA,YACjC,cAAc;AAAA,YACdA;AAAA,UACF;AACA,cAAI,mBAAmB,0BAA0B,GAAG;AAClD,kBAAM,wBAAkD;AAAA,cACtD,UAAU;AAAA,YACZ;AAEA,gBAAI,CAAC,SAAS,mBAAmB,aAAa,EAAG,IAAI,GAAG;AACtD,oCAAsB,OAAO;AAAA,gBAC3B,MAAM,mBAAmB,aAAa,EAAG,KAAK;AAAA,cAChD;AAAA,YACF;AAEA,mBAAO;AAAA,UACT,OAAO;AACL,+BAAmB,OAAO,eAAe,CAAC;AAAA,UAC5C;AAAA,QACF;AAAA,MACF;AAGA,YAAM,2BAA2B,mBAAmB;AAAA,QAClD,CAAC,MAAM,CAAC,EAAE,KAAK;AAAA,MACjB,EAAE,CAAC;AACH,YAAM,YAAY;AAAA,QAChB;AAAA,QACA;AAAA,MACF,EAAE,MAAM,iBAAiB,yBAAyB,mBAAmB,CAAC;AACtE,YAAM,kBAA4C;AAAA,QAChD,UACE,yBAAyB,KAAK,SAAS,cAAc,KAAM,OACvD,cAAc,WACd,sBAAsB,cAAc,UAAU,SAAS;AAAA,MAC/D;AACA,UAAI,CAAC,SAAS,yBAAyB,IAAI,GAAG;AAC5C,wBAAgB,OAAO,EAAE,MAAM,yBAAyB,KAAK,KAAK;AAAA,MACpE;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO,WAAW,IAAI,CAAC,MAAM;AAC3B,QAAI,WAAW,CAAC,EAAG,QAAO,iBAAiB,CAAC;AAG5C,UAAM,gBAAgB;AAAA,MACpB,EAAE,GAAG,IAAI,CAAC,QAAQ;AAAA,QAChB,GAAG;AAAA,QACH,MAAM,YAAY,GAAG,MAAM,MAAM,GAAG,MAAM,gBAAgB;AAAA,MAC5D,EAAE;AAAA,IACJ;AAEA,WAAO,iBAAiB,cAAc,CAAC,CAAE;AAAA,EAC3C,CAAC;AACH;AAEO,SAAS,sBACd,YAIA,QAIA;AACA,MAAI,WAAW,WAAW;AACxB,WAAO;AAAA,MACL,KAAK;AAAA,QACH,UAAU,wBAAwB;AAAA,QAClC,MAAM,YAAY;AAAA,MACpB;AAAA,MACA,YAAY,CAAC;AAAA,IACf;AAEF,MAAI,WAAW,WAAW,GAAG;AAC3B,QAAI,WAAW,WAAW,CAAC,CAAE;AAC3B,aAAO;AAAA,QACL,KAAK;AAAA,UACH,GAAG,WAAW,CAAC;AAAA,UACf,MAAM,YAAY,WAAW,CAAC,EAAE,MAAM,IAAI;AAAA,QAC5C;AAAA,QACA,YAAY,CAAC;AAAA,MACf;AAAA,EACJ;AAEA,QAAM,aAAa,wBAAwB,GAAG,UAAU;AAExD,QAAM,oBAAoB,2BAA2B,YAAY,UAAU;AAE3E,QAAM,MAA6B,CAAC;AACpC,aAAW,SAAS,mBAAmB;AACrC,UAAM,YAAY,iCAAiC,KAAK,KAAK;AAC7D,QAAI,cAAc,QAAW;AAC3B,UAAI,KAAK;AAAA,QACP,GAAG;AAAA,QACH,MAAM,YAAY,MAAM,MAAM,IAAI;AAAA,MACpC,CAAC;AAAA,IACH,OAAO;AACL,YAAM,OAAO,cAAc,WAAW,OAAO,MAAM;AACnD,gBAAU,WAAW,KAAK;AAC1B,gBAAU,OAAO,YAAY,KAAK,MAAM,IAAI;AAAA,IAC9C;AAAA,EACF;AACA,MAAI,IAAI,WAAW,GAAG;AACpB,WAAO,EAAE,KAAK,IAAI,CAAC,GAAI,WAAW;AAAA,EACpC;AACA,SAAO,EAAE,KAAK,EAAE,KAAK,IAAI,GAAG,WAAW;AACzC;AAEO,SAAS,sCACd,KACA,YACA,QAC6E;AAC7E,QAAM,gBAAgB,WAAW,GAAG,IAAI,IAAI,MAAM,CAAC,GAAG;AACtD,QAAM,SAGA,CAAC;AACP,QAAM,sBAAsB,oBAAI,IAAyB;AAEzD,aAAW,QAAQ,YAAY;AAC7B,UAAM,WAAW,UAAU,IAAI;AAC/B,UAAM,OAA8B,CAAC;AACrC,UAAM,iBAAiB,cAAc;AAAA,MACnC,CAAC,MAAM,CAAC,oBAAoB,IAAI,CAAC;AAAA,IACnC;AACA,QAAI,eAAe,WAAW,EAAG;AAEjC,mBAAe,QAAQ,CAAC,MAAM;AAE5B,YAAM,aAAa,iCAAiC,UAAU,CAAC;AAE/D,UAAI,eAAe,QAAW;AAC5B,4BAAoB,IAAI,CAAC;AACzB,aAAK,KAAK,CAAC;AACX,iBAAS,OAAO,SAAS,QAAQ,UAAU,GAAG,CAAC;AAAA,MACjD;AAAA,IACF,CAAC;AAGD,UAAM,cAAc,aAAa,QAAQ,EAAE,IAAI,CAAC,UAAU;AACxD,YAAM,eAAyC;AAAA,QAC7C,UAAU,wBAAwB;AAAA,MACpC;AAEA,UAAI,MAAM,MAAM;AACd,qBAAa,OAAO,EAAE,MAAM,MAAM,KAAK,KAAK;AAAA,MAC9C;AACA,aAAO,KAAK,OAAO,CAAC,KAAK,MAAM;AAC7B,cAAM,aAAa,iCAAiC,MAAM,CAAC;AAG3D,cAAM,kBAAkB,iBAAiB,GAAG,UAAU;AACtD,cAAM,0BAAsB,YAAAC,SAAI,gBAAgB,EAAE,QAAQ,CAAC,EAAE;AAAA,UAC3D;AAAA,QACF;AACA,cAAM,WAAqC;AAAA,UACzC,UAAU;AAAA,YACR;AAAA,cACE,MAAM;AAAA,cACN,OAAO;AAAA,gBACL,MAAM;AAAA,gBACN,SAAS,oBAAoB,SAAS;AAAA,cACxC;AAAA,YACF;AAAA,gBACA,YAAAA,SAAI,gBAAgB,MAAM,QAAQ,CAAC,EAAE;AAAA,cACnC,gBAAgB,WAAW,QAAQ;AAAA,YACrC;AAAA,UACF;AAAA,QACF;AACA,YAAI,MAAM,QAAQ,CAAC,SAAS,MAAM,IAAI,GAAG;AACvC,mBAAS,OAAO,EAAE,MAAM,MAAM,KAAK,KAAK;AAAA,QAC1C;AACA,eAAO,cAAc,KAAK,UAAU,MAAM;AAAA,MAC5C,GAAG,YAAY;AAAA,IACjB,CAAC;AAED,QAAI,KAAK,SAAS,YAAY,SAAS,GAAG;AACxC,YAAM,aAGJ,KAAK,SAAS,IACV;AAAA,QACE,KAAK,KAAK,IAAI,mBAAmB;AAAA,MACnC,IACA,oBAAoB,KAAK,CAAC,CAAE;AAClC,aAAO,KAAK;AAAA,QACV,IAAI,CAAC,YAAY,GAAG,WAAW;AAAA,MACjC,CAAC;AAAA,IACH,OAEK;AACH,aAAO,KAAK,oBAAoB,KAAK,CAAC,CAAE,CAAC;AAAA,IAC3C;AAAA,EACF;AAGA,gBACG,OAAO,CAAC,MAAM,CAAC,oBAAoB,IAAI,CAAC,CAAC,EACzC,QAAQ,CAAC,MAAM,OAAO,KAAK,oBAAoB,CAAC,CAAC,CAAC;AAErD,SAAO;AACT;AAEO,SAAS,0BACd,YAIA,QACiE;AACjE,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,YAAY,WAAW,CAAC,CAAE;AAAA,EACnC;AAEA,QAAM,EAAE,KAAK,WAAW,IAAI,sBAAsB,YAAY,MAAM;AAEpE,QAAM,YAAY;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,MAAI,UAAU,WAAW,GAAG;AAC1B,WAAO,YAAY,UAAU,CAAC,CAAE;AAAA,EAClC,OAAO;AACL,WAAO,EAAE,KAAK,UAAU,IAAI,WAAW,EAAE;AAAA,EAC3C;AACF;;;AGrWA,IAAAC,cAAgB;AAmCT,IAAM,UAAN,MAAM,QAAO;AAAA;AAAA;AAAA;AAAA;AAAA,EAmFlB,YAAY,SAAkB;AA/E9B;AAAA;AAAA;AAAA,oCAAqB,CAAC;AAItB;AAAA;AAAA;AAAA,mCAA8B;AAAA,MAC5B,iBAAiB,oBAAI,IAAI;AAAA,MACzB,kBAAkB,oBAAI,IAAI;AAAA,IAC5B;AAIA;AAAA;AAAA;AAAA,uCAA4B,CAAC;AAI7B;AAAA;AAAA;AAAA,oCAAsB,CAAC;AAIvB;AAAA;AAAA;AAAA,oCAAuB,CAAC;AAIxB;AAAA;AAAA;AAAA,kCAAkB,CAAC;AAInB;AAAA;AAAA;AAAA,uCAAmC,CAAC;AAQpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6CE,YAAO,WAAW,IAAI,MAAM,CAAC;AAC7B,QAAI,SAAS;AACX,WAAK,MAAM,OAAO;AAAA,IACpB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAzCA,IAAI,aAA6C;AAC/C,WAAO,QAAO,YAAY,IAAI,IAAI;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAiBQ,eAAuB;AAC7B,WAAO,QAAO,WAAW,IAAI,IAAI;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKQ,2BAAmC;AACzC,UAAM,UAAU,KAAK,aAAa;AAClC,YAAO,WAAW,IAAI,MAAM,UAAU,CAAC;AACvC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBQ,wBACN,kBACA,WACM;AAGN,QAAI,CAAC,oBAAoB,CAAC,iBAAiB,kBAAmB;AAC9D,UAAM,gBAAgB,iBAAiB,mBACnC,KAAK,EACN,MAAM,wBAAwB;AAGjC,QAAI,eAAe,QAAQ;AACzB,YAAM,QAAQ,mBAAmB,cAAc,OAAO,QAAS;AAC/D,YAAM,OAAO,cAAc,OAAO;AAClC,YAAM,OAAO,iBAAiB,iBAAiB;AAC/C,UAAI,CAAC,SAAU,MAAM,SAAS,WAAW,MAAM,MAAM,SAAS,QAAS;AACrE,cAAM,IAAI;AAAA,UACR,iBAAiB,mBAAmB,KAAK;AAAA,UACzC;AAAA,QACF;AAAA,MACF;AACA,YAAM,YAA+B;AAAA,QACnC,UAAU;AAAA,MACZ;AACA,UAAI,KAAM,WAAU,OAAO;AAC3B,UAAI,KAAM,WAAU,OAAO;AAC3B,gBAAU,KAAK;AAAA,QACb,MAAM;AAAA,QACN,OAAO,KAAK,YAAY,KAAK,SAAS,IAAI;AAAA,MAC5C,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,eAAe,MAA0B;AAC/C,UAAM,YAAwB,CAAC;AAC/B,QAAI,SAAS;AACb,UAAM,cAAc,IAAI,OAAO,uBAAuB,QAAQ,GAAG;AAEjE,eAAW,SAAS,KAAK,SAAS,WAAW,GAAG;AAC9C,YAAM,MAAM,MAAM;AAElB,UAAI,MAAM,QAAQ;AAChB,kBAAU,KAAK,EAAE,MAAM,QAAQ,OAAO,KAAK,MAAM,QAAQ,GAAG,EAAE,CAAC;AAAA,MACjE;AAEA,WAAK,wBAAwB,MAAM,QAAQ,SAAS;AACpD,eAAS,MAAM,MAAM,CAAC,EAAE;AAAA,IAC1B;AAEA,QAAI,SAAS,KAAK,QAAQ;AACxB,gBAAU,KAAK,EAAE,MAAM,QAAQ,OAAO,KAAK,MAAM,MAAM,EAAE,CAAC;AAAA,IAC5D;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,wBACN,aAC4B;AAC5B,QAAI,gBAAgB,YAAY,MAAM,wBAAwB;AAC9D,UAAM,aAAyC,CAAC;AAChD,WAAO,eAAe,QAAQ;AAC5B,YAAM,QAAQ,cAAc,OAAO,WAC/B,mBAAmB,cAAc,OAAO,QAAQ,IAChD;AACJ,YAAM,OAAO,cAAc,OAAO;AAClC,UAAI,OAAO;AACT,cAAM,cAAwC,EAAE,UAAU,MAAM;AAChE,YAAI,MAAM;AACR,cAAI,KAAK,WAAW,GAAG,GAAG;AACxB,wBAAY,OAAO;AAAA,cACjB,MAAM,KAAK,UAAU,CAAC;AAAA,cACtB,kBAAkB;AAAA,YACpB;AAAA,UACF,OAAO;AACL,wBAAY,OAAO,EAAE,MAAM,KAAK;AAAA,UAClC;AAAA,QACF;AACA,mBAAW,KAAK,WAAW;AAAA,MAC7B,OAAO;AACL,cAAM,IAAI,sBAAsB,WAAW;AAAA,MAC7C;AACA,sBAAgB,cAAc,OAAO,cACjC,cAAc,OAAO,YAAY,MAAM,wBAAwB,IAC/D;AAAA,IACN;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,yCACN,uBACA,OACM;AACN,UAAM,eAAwC,CAAC;AAC/C,QAAI,aAAa;AACjB,WAAO,MAAM;AACX,YAAM,QAAQ,WAAW;AAAA,QACvB,aAAa,SAAS,IAClB,oCACA;AAAA,MACN;AACA,UAAI,CAAC,OAAO,OAAQ;AACpB,YAAM,SAAS,MAAM;AAIrB,UAAI,OAAQ,OAAO,mBAAmB,OAAO;AAI7C,YAAM,cAAc,OAAO;AAE3B,YAAM,YAAY,OAAO;AACzB,YAAM,YAAY,cAAc,UAAa,UAAU,SAAS,GAAG;AACnE,YAAM,QAA0B,CAAC;AACjC,UAAI,cAAc,UAAa,UAAU,SAAS,GAAG,GAAG;AACtD,cAAM,KAAK,UAAU;AAAA,MACvB;AACA,UAAI,cAAc,UAAa,UAAU,SAAS,GAAG,GAAG;AACtD,cAAM,KAAK,QAAQ;AAAA,MACrB;AACA,UACG,cAAc,UAAa,UAAU,SAAS,GAAG,KAClD,OAAO,wBACP;AACA,cAAM,KAAK,QAAQ;AAAA,MACrB;AAEA,UAAI,SAAuC;AAE3C,UAAI,MAAM,SAAS,QAAQ,GAAG;AAC5B,iBAAS,EAAE,MAAM,GAAG,IAAI,QAAQ;AAChC,eAAO,KAAK,UAAU,KAAK,YAAY,GAAG,IAAI,CAAC;AAAA,MACjD;AAEA,YAAM,aAAa,KAAK,MAAM,oBAAoB;AAClD,UAAI,UAAU;AACd,UACE,cACA,WAAW,OAAQ,mBAAoB,KAAK,EAAE,SAAS,KACvD,WAAW,OAAQ,sBAAuB,KAAK,EAAE,SAAS,GAC1D;AACA,mBAAW,WAAW,OAAQ,mBAAoB,KAAK;AACvD,sBAAc,WAAW,OAAQ,sBAAuB,KAAK;AAAA,MAC/D,OAAO;AACL,mBAAW;AACX,sBAAc;AAAA,MAChB;AAEA,YAAM,gBAA4B;AAAA,QAChC,MAAM;AAAA,MACR;AAEA,UAAI,aAAa;AACf,sBAAc,cAAc;AAAA,MAC9B;AACA,UAAI,MAAM,SAAS,GAAG;AACpB,sBAAc,QAAQ;AAAA,MACxB;AACA,UAAI,QAAQ;AACV,sBAAc,SAAS;AAAA,MACzB;AAEA,YAAM,YAAY;AAAA,QAChB,KAAK;AAAA,QACL;AAAA,QACA;AAAA,MACF;AAIA,UAAI,eAAkD;AACtD,UAAI,OAAO,oBAAoB;AAC7B,cAAM,mBAAmB,KAAK;AAAA,UAC5B,OAAO;AAAA,QACT;AACA,cAAM,CAAC,SAAS,GAAG,IAAI,IAAI;AAC3B,YAAI,SAAS;AACX,yBAAe;AAAA,YACb,GAAG;AAAA,YACH,UAAU,OAAO,+BAA+B;AAAA,UAClD;AACA,cAAI,KAAK,SAAS,GAAG;AACnB,yBAAa,cAAc;AAAA,UAC7B;AAAA,QACF;AAAA,MACF;AAEA,YAAM,cAAqC;AAAA,QACzC,OAAO;AAAA,QACP;AAAA,MACF;AAEA,YAAM,OAAO,OAAO,gBAAgB,KAAK;AACzC,UAAI,MAAM;AACR,oBAAY,OAAO;AAAA,MACrB;AACA,UAAI,cAAc;AAChB,eAAO,OAAO,aAAa,YAAY;AAAA,MACzC;AACA,mBAAa,KAAK,WAAW;AAC7B,mBAAa,OAAO,yBAAyB;AAAA,IAC/C;AAGA,QAAI,aAAa,SAAS,GAAG;AAC3B,YAAM,sBAAsB,aAAa,IAAI,CAAC,QAAQ,IAAI,KAAK;AAC/D,iBAAW,mBAAmB,qBAAqB;AACjD,cAAM,aAAa,KAAK,YAAY,eAAe;AAGnD,YAAI,YAAY;AACd,cAAI,CAAC,WAAW,cAAc;AAC5B,uBAAW,eAAe,IAAI;AAAA,cAC5B,oBAAoB,OAAO,CAAC,UAAU,UAAU,eAAe;AAAA,YACjE;AAAA,UACF,OAAO;AACL,uBAAW,eAAe;AAAA,cACxB,WAAW;AAAA,cACX,IAAI;AAAA,gBACF,oBAAoB;AAAA,kBAClB,CAAC,UAAU,UAAU;AAAA,gBACvB;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,KAAK,mBAAmB,KAAK,yBAAyB,CAAC;AAG7D,UAAM,UAA0B;AAAA,MAC9B,MAAM;AAAA,MACN;AAAA,MACA;AAAA,IACF;AACA,UAAM,KAAK,OAAO;AAElB,QAAI,aAAa,SAAS,GAAG;AAC3B,WAAK,QAAQ,gBAAgB,IAAI,IAAI,YAAY;AAAA,IACnD;AAAA,EACF;AAAA,EAEQ,6BACN,uBACA,OACM;AACN,UAAM,QAAQ,sBAAsB,MAAM,2BAA2B;AAGrE,QAAI,CAAC,OAAO,OAAQ;AACpB,UAAM,SAAS,MAAM;AAIrB,UAAM,WAAW,OAAO;AACxB,QAAI,OAAQ,OAAO,oBAAoB,OAAO;AAI9C,UAAM,cAAc,OAAO;AAE3B,UAAM,YAAY,OAAO;AACzB,UAAM,YAAY,cAAc,UAAa,UAAU,SAAS,GAAG;AACnE,UAAM,QAA0B,CAAC;AACjC,QAAI,cAAc,UAAa,UAAU,SAAS,GAAG,GAAG;AACtD,YAAM,KAAK,UAAU;AAAA,IACvB;AACA,QAAI,cAAc,UAAa,UAAU,SAAS,GAAG,GAAG;AACtD,YAAM,KAAK,QAAQ;AAAA,IACrB;AACA,QACG,cAAc,UAAa,UAAU,SAAS,GAAG,KAClD,OAAO,yBACP;AACA,YAAM,KAAK,QAAQ;AAAA,IACrB;AAEA,QAAI,SAAuC;AAE3C,QAAI,MAAM,SAAS,QAAQ,GAAG;AAC5B,eAAS,EAAE,MAAM,GAAG,IAAI,QAAQ;AAChC,aAAO,KAAK,UAAU,KAAK,YAAY,GAAG,IAAI,CAAC;AAAA,IACjD;AAEA,UAAM,aAAa,KAAK,MAAM,oBAAoB;AAClD,QAAI,UAAU;AACd,QACE,cACA,WAAW,OAAQ,mBAAoB,KAAK,EAAE,SAAS,KACvD,WAAW,OAAQ,sBAAuB,KAAK,EAAE,SAAS,GAC1D;AACA,iBAAW,WAAW,OAAQ,mBAAoB,KAAK;AACvD,oBAAc,WAAW,OAAQ,sBAAuB,KAAK;AAAA,IAC/D,OAAO;AACL,iBAAW;AACX,oBAAc;AAAA,IAChB;AAEA,UAAM,gBAA4B;AAAA,MAChC,MAAM;AAAA,IACR;AAEA,QAAI,aAAa;AACf,oBAAc,cAAc;AAAA,IAC9B;AACA,QAAI,MAAM,SAAS,GAAG;AACpB,oBAAc,QAAQ;AAAA,IACxB;AACA,QAAI,QAAQ;AACV,oBAAc,SAAS;AAAA,IACzB;AAEA,UAAM,YAAY;AAAA,MAChB,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAIA,QAAI,eAAkD;AACtD,QAAI,OAAO,qBAAqB;AAC9B,YAAM,mBAAmB,KAAK;AAAA,QAC5B,OAAO;AAAA,MACT;AACA,YAAM,CAAC,SAAS,GAAG,IAAI,IAAI;AAC3B,qBAAe;AAAA,QACb,GAAG;AAAA;AAAA,QACH,UAAU,OAAO,gCAAgC;AAAA,MACnD;AACA,UAAI,KAAK,SAAS,GAAG;AACnB,qBAAa,cAAc;AAAA,MAC7B;AAAA,IACF;AAEA,UAAM,cAAqC;AAAA,MACzC,OAAO;AAAA,MACP;AAAA,IACF;AAEA,QAAI,cAAc;AAChB,aAAO,OAAO,aAAa,YAAY;AAAA,IACzC;AAEA,UAAM,uBAAuB,KAAK,QAAQ,iBAAiB,IAAI,QAAQ;AAEvE,aAAS,8BACP,aACA,eACA,mBACA;AACA,YAAM,aAAa,YAAY,aAAa;AAG5C,UAAI,YAAY;AACd,YAAI,WAAW,iBAAiB,QAAW;AACzC,qBAAW,eAAe,oBAAI,IAAI,CAAC,iBAAiB,CAAC;AAAA,QACvD,OAAO;AACL,qBAAW,aAAa,IAAI,iBAAiB;AAAA,QAC/C;AAAA,MACF;AAAA,IACF;AACA,QAAI,sBAAsB;AACxB,iBAAW,OAAO,sBAAsB;AACtC,sCAA8B,KAAK,aAAa,IAAI,OAAO,SAAS;AACpE,sCAA8B,KAAK,aAAa,WAAW,IAAI,KAAK;AAAA,MACtE;AAAA,IACF;AACA,UAAM,KAAK,mBAAmB,KAAK,yBAAyB,CAAC;AAG7D,UAAM,UAA0B;AAAA,MAC9B,MAAM;AAAA,MACN;AAAA,MACA,OAAO;AAAA,MACP,cAAc,CAAC,WAAW;AAAA,IAC5B;AACA,UAAM,KAAK,OAAO;AAGlB,UAAM,oBAAoB,UAAU,WAAW;AAC/C,sBAAkB,SAAS;AAC3B,UAAM,iBAAiB,KAAK,QAAQ,iBAAiB,IAAI,QAAQ;AACjE,QAAI,CAAC,gBAAgB;AACnB,WAAK,QAAQ,iBAAiB,IAAI,UAAU,CAAC,iBAAiB,CAAC;AAAA,IACjE,OAAO;AACL,qBAAe,KAAK,iBAAiB;AAAA,IACvC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,kCAAwC;AAE9C,eAAW,OAAO,KAAK,aAAa;AAClC,aAAO,IAAI;AACX,aAAO,IAAI;AAAA,IACb;AAGA,UAAM,4BAA4B,KAAK,wBAAwB;AAG/D,UAAM,iBAAiB,oBAAI,IAAY;AAIvC,eAAW,YAAY,2BAA2B;AAChD,YAAM,MAAM,KAAK,YAAY;AAAA,QAC3B,CAACC,MAAKC,OAAMD,KAAI,SAAS,SAAS,QAAQ,CAAC,eAAe,IAAIC,EAAC;AAAA,MACjE;AACA,qBAAe,IAAI,GAAG;AACtB,YAAM,MAAM,KAAK,YAAY,GAAG;AAChC,UAAI,SAAS,YAAY;AACvB,YAAI,aAAa,SAAS;AAAA,MAC5B;AACA,UAAI,SAAS,eAAe;AAC1B,YAAI,gBAAgB;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA6BA,wBACE,SACc;AACd,UAAM,EAAE,SAAS,MAAM,QAAQ,IAAI,WAAW,CAAC;AAG/C,UAAM,oBACJ,YAAY,UACP,MAAM;AACL,YAAM,MACJ,OAAO,YAAY,WACf,UACA,KAAK,SAAS,QAAQ,OAAO;AACnC,aAAO,OAAO,KAAK,MAAM,KAAK,SAAS,SACnC,CAAC,KAAK,SAAS,GAAG,CAAE,IACpB,CAAC;AAAA,IACP,GAAG,IACH,KAAK;AAeX,UAAM,mBAAmB,oBAAI,IAG3B;AAGF,UAAM,kBAAkB,oBAAI,IAAY;AACxC,UAAM,oBAAoB,oBAAI,IAAY;AAE1C,eAAW,kBAAkB,mBAAmB;AAC9C,YAAM,WAAW,eAAe,QAAQ;AAAA,QACtC,CAAC,SAAuB,KAAK,SAAS;AAAA,MACxC;AAGA,YAAM,iBACJ,SAAS,SACL,WACA,OAAO,SAAS,WACd,QAAQ,KAAK,OAAO,SAAS,SAC3B,CAAC,SAAS,IAAI,CAAE,IAChB,CAAC,IACH,SAAS,SAAS,IAAI,IACpB,CAAC,IAAI,IACL,CAAC;AAEX,iBAAW,eAAe,gBAAgB;AACxC,mBAAW,QAAQ,YAAY,MAAM;AAAA,UACnC,CAACC,UAAiCA,MAAK,SAAS;AAAA,QAClD,GAAG;AACD,gBAAM,YAAY,WAAW,QAAQ,KAAK,UAAU;AACpD,gBAAM,oBAAoB,YACtB,KAAK,QAAQ,iBAAiB,IAAI,KAAK,KAAM,IAC7C;AAGJ,cAAI,mBAAmB;AACvB,cAAI,aAAa;AACjB,cAAI,oBAAoB;AAExB,cAAI,WAAW;AACb,kBAAM,cAAc,SAAS,kBAAkB,IAAI,KAAK,KAAM;AAC9D,gCAAoB,gBAAgB;AACpC,kBAAM,cAAc,eAAe;AACnC,yBAAa,oBAAoB,WAAW,GAAG,WAAW,KAAK;AAAA,UACjE,OAAO;AACL,kBAAM,aAAa,SAAS,iBAAiB,IAAI,KAAK,EAAE;AACxD,gCAAoB,eAAe;AACnC,+BAAmB,cAAc;AACjC,yBAAa;AAAA,UACf;AAEA,gBAAM,cAAc,KAAK,aAAa,gBAAgB;AACtD,cAAI,CAAC,eAAe,CAAC,WAAY;AAEjC,0BAAgB,IAAI,YAAY,KAAK;AAGrC,gBAAM,UAAU,YAAY,oBAAqB,KAAK;AACtD,qBAAW,OAAO,SAAS;AACzB,8BAAkB,IAAI,IAAI,KAAK;AAAA,UACjC;AAEA,cAAI,CAAC,YAAY,SAAU;AAG3B,gBAAM,UAAoC;AAAA,YACxC,UAAU,YAAY;AAAA,YACtB,GAAI,YAAY,QAAQ;AAAA,cACtB,MAAM,YAAY;AAAA,YACpB;AAAA,UACF;AACA,gBAAM,gBAAgB,YAAY,aAAa,SAC3C,EAAE,IAAI,CAAC,SAAS,GAAG,YAAY,WAAW,EAAE,IAC5C;AAGJ,cAAI;AACJ,cAAI,CAAC,qBAAqB,QAAQ,SAAS,GAAG;AAC5C,8BAAkB,QACf;AAAA,cAAO,CAAC,QACP,YACI,IAAI,WAAW,KAAK,KACpB,IAAI,UAAU,YAAY;AAAA,YAChC,EACC,IAAI,CAAC,aAAa;AACjB,oBAAM,MAAgC,EAAE,OAAO,SAAS,MAAM;AAC9D,kBAAI,SAAS,UAAU;AACrB,sBAAM,SAAgC;AAAA,kBACpC,UAAU,SAAS;AAAA,kBACnB,GAAI,SAAS,QAAQ;AAAA,oBACnB,MAAM,SAAS,KAAK;AAAA,kBACtB;AAAA,kBACA,GAAI,SAAS,eAAe;AAAA,oBAC1B,aAAa,SAAS,YAAY;AAAA,sBAChC,CAAC,OAAO,YAAY,EAAE;AAAA,oBACxB;AAAA,kBACF;AAAA,gBACF;AACA,oBAAI,aAAa,CAAC,MAAM;AAAA,cAC1B;AACA,qBAAO;AAAA,YACT,CAAC;AAAA,UACL;AAKA,gBAAM,aAAa,wBAAwB,eAAe,KAAK;AAC/D,cAAI;AACJ,cAAI,WAAW;AACb,kBAAM,eAAe,YAAY,YAAY,MAAM,IAAI;AACvD,wBAAY,SAAS,KAAK,KAAK,IAAI,UAAU,IAAI,aAAa,IAAI;AAAA,UACpE,WAAW,YAAY;AAErB,kBAAM,eAAe,YAAY,YAAY,MAAM,IAAI;AACvD,wBAAY,GAAG,UAAU,IAAI,aAAa,IAAI;AAAA,UAChD,OAAO;AAEL,wBAAY;AAAA,UACd;AAEA,cAAI,CAAC,iBAAiB,IAAI,YAAY,KAAK,GAAG;AAC5C,6BAAiB,IAAI,YAAY,OAAO,oBAAI,IAAI,CAAC;AAAA,UACnD;AACA,gBAAM,eAAe,iBAAiB,IAAI,YAAY,KAAK;AAC3D,cAAI,CAAC,aAAa,IAAI,SAAS,GAAG;AAChC,yBAAa,IAAI,WAAW;AAAA,cAC1B,YAAY,CAAC;AAAA,cACb,uBAAuB,oBAAI,IAAI;AAAA,YACjC,CAAC;AAAA,UACH;AACA,gBAAM,QAAQ,aAAa,IAAI,SAAS;AAExC,gBAAM,WAAW,KAAK,aAAa;AAGnC,qBAAW,OAAO,mBAAmB,CAAC,GAAG;AACvC,gBAAI,CAAC,MAAM,sBAAsB,IAAI,IAAI,KAAK,GAAG;AAC/C,oBAAM,sBAAsB,IAAI,IAAI,OAAO,CAAC,CAAC;AAAA,YAC/C;AACA,uBAAW,UAAU,IAAI,cAAc,CAAC,GAAG;AACzC,oBAAM,WAAW,eAAe;AAAA,gBAC9B,UAAU,OAAO;AAAA,gBACjB,MAAM,OAAO;AAAA,cACf,CAAC;AACD,kBAAI,OAAO,aAAa,QAAQ;AAC9B,sBAAM,YAAwC;AAAA,kBAC5C;AAAA,kBACA,GAAG,OAAO,YAAY,IAAI,CAAC,OAAO,eAAe,EAAE,CAAC;AAAA,gBACtD;AACA,sBAAM,sBACH,IAAI,IAAI,KAAK,EACb,KAAK,EAAE,IAAI,UAAU,CAAC;AAAA,cAC3B,OAAO;AACL,sBAAM,sBAAsB,IAAI,IAAI,KAAK,EAAG,KAAK,QAAQ;AAAA,cAC3D;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,UAAM,SAAuB,CAAC;AAE9B,aAAS,QAAQ,GAAG,QAAQ,KAAK,YAAY,QAAQ,SAAS;AAC5D,UAAI,CAAC,kBAAkB,IAAI,KAAK,EAAG;AAEnC,YAAM,OAAO,KAAK,YAAY,KAAK;AACnC,YAAM,MAAkB;AAAA,QACtB,MAAM,KAAK;AAAA,QACX,GAAI,KAAK,eAAe,EAAE,aAAa,KAAK,YAAY;AAAA,QACxD,GAAI,KAAK,SAAS,EAAE,OAAO,KAAK,MAAM;AAAA,QACtC,GAAI,KAAK,UAAU,EAAE,QAAQ,KAAK,OAAO;AAAA,MAC3C;AAEA,UAAI,gBAAgB,IAAI,KAAK,GAAG;AAC9B,YAAI,gBAAgB;AAEpB,cAAM,eAAe,iBAAiB,IAAI,KAAK;AAC/C,YAAI,cAAc;AAChB,gBAAM,iBAGA,CAAC;AAEP,qBAAW,CAAC,EAAE,KAAK,KAAK,cAAc;AACpC,kBAAM,SAAS;AAAA,cACb,MAAM;AAAA,cACN,KAAK;AAAA,YACP;AACA,kBAAM,YAAY,sBAAsB,MAAM;AAG9C,kBAAM,eACJ,MAAM,sBAAsB,OAAO,IAC/B,CAAC,GAAG,MAAM,qBAAqB,EAAE,IAAI,CAAC,CAAC,QAAQ,OAAO,OAAO;AAAA,cAC3D,OAAO;AAAA,cACP,GAAI,QAAQ,SAAS,KAAK;AAAA,gBACxB,YAAY;AAAA,kBACV,0BAA0B,SAAS,KAAK,UAAU;AAAA,gBACpD,EAAE;AAAA;AAAA,kBAEA,CAAC,SAAU,cAAc,OAAO,CAAC,IAAI,IAAI,KAAK;AAAA,gBAChD;AAAA,cACF;AAAA,YACF,EAAE,IACF;AAEN,uBAAW,MAAM,WAAW;AAC1B,kBAAI,SAAS,IAAI;AACf,+BAAe,KAAK;AAAA,kBAClB,KAAK,GAAG;AAAA,kBACR,GAAI,GAAG,aAAa,UAAU;AAAA,oBAC5B,aAAa,GAAG;AAAA,kBAClB;AAAA,kBACA,GAAI,cAAc,UAAU,EAAE,aAAa;AAAA,gBAC7C,CAAC;AAAA,cACH,OAAO;AACL,+BAAe,KAAK;AAAA,kBAClB,GAAI;AAAA,kBACJ,GAAI,cAAc,UAAU,EAAE,aAAa;AAAA,gBAC7C,CAAC;AAAA,cACH;AAAA,YACF;AAAA,UACF;AAGA,cAAI,eAAe,SAAS,GAAG;AAC7B,gBAAI,aAAa;AAAA,UACnB;AAAA,QACF;AAAA,MACF;AAEA,aAAO,KAAK,GAAG;AAAA,IACjB;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,SAAiB;AAErB,UAAM,eAAe,QAClB,QAAQ,eAAe,EAAE,EACzB,QAAQ,cAAc,EAAE,EACxB,QAAQ,mBAAmB,EAAE,EAC7B,KAAK,EACL,MAAM,UAAU;AAGnB,UAAM,EAAE,UAAU,UAAU,WAAW,IACrC,gBAAgB,OAAO;AACzB,SAAK,WAAW;AAChB,SAAK,WAAW;AAChB,QAAI,WAAY,SAAO,YAAY,IAAI,MAAM,UAAU;AAGvD,QAAI,kBAAkB;AACtB,QAAI,UAAmB,IAAI,QAAQ;AACnC,UAAM,QAAuB,CAAC;AAC9B,QAAI,WAAW;AACf,QAAI,SAAS;AAGb,eAAW,QAAQ,cAAc;AAE/B,UAAI,KAAK,KAAK,EAAE,WAAW,GAAG;AAC5B,0BAAkB,SAAS,KAAK;AAChC;AAAA,UACE;AAAA,UACA,WAAW,KAAK,eAAe,QAAQ,IAAI,CAAC;AAAA,QAC9C;AACA,mBAAW;AACX,0BAAkB;AAClB,iBAAS;AACT;AAAA,MACF;AAGA,UAAI,KAAK,WAAW,GAAG,GAAG;AACxB,0BAAkB,SAAS,KAAK;AAChC;AAAA,UACE;AAAA,UACA,WAAW,KAAK,eAAe,QAAQ,IAAI,CAAC;AAAA,QAC9C;AACA,mBAAW;AAEX,YAAI,KAAK,SAAS,WAAW,KAAK,QAAQ,QAAQ,GAAG;AACnD,kBAAQ,OAAO,KAAK,QAAQ,YAAY,EAAE,EAAE,KAAK;AAAA,QACnD,OAAO;AAEL,cAAI,CAAC,QAAQ,QAAQ,GAAG;AACtB,iBAAK,SAAS,KAAK,OAAO;AAAA,UAC5B;AACA,oBAAU,IAAI,QAAQ,KAAK,QAAQ,YAAY,EAAE,EAAE,KAAK,CAAC;AAAA,QAC3D;AACA,0BAAkB;AAClB,iBAAS;AACT;AAAA,MACF;AAGA,UAAI,mBAAmB,KAAK,WAAW,GAAG,GAAG;AAC3C,0BAAkB,SAAS,KAAK;AAChC,mBAAW,KAAK,UAAU,CAAC,EAAE,KAAK;AAClC,iBAAS;AACT,0BAAkB;AAClB;AAAA,MACF;AAGA,UAAI,QAAQ;AACV,YAAI,KAAK,WAAW,GAAG,GAAG;AACxB,sBAAY,MAAM,KAAK,UAAU,CAAC,EAAE,KAAK;AAAA,QAC3C,OAAO;AACL,sBAAY,MAAM,KAAK,KAAK;AAAA,QAC9B;AACA,0BAAkB;AAClB;AAAA,MACF;AAGA,UAAI,SAAS;AACb,iBAAW,SAAS,KAAK,SAAS,WAAW,GAAG;AAC9C,cAAM,MAAM,MAAM;AAElB,YAAI,MAAM,QAAQ;AAChB,gBAAM,KAAK,EAAE,MAAM,QAAQ,OAAO,KAAK,MAAM,QAAQ,GAAG,EAAE,CAAC;AAAA,QAC7D;AAEA,cAAM,SAAS,MAAM;AAGrB,YAAI,OAAO,mBAAmB,OAAO,iBAAiB;AACpD,eAAK,yCAAyC,MAAM,CAAC,GAAG,KAAK;AAAA,QAC/D,WAES,OAAO,oBAAoB,OAAO,kBAAkB;AAC3D,eAAK,6BAA6B,MAAM,CAAC,GAAG,KAAK;AAAA,QACnD,WAES,OAAO,iBAAiB,OAAO,eAAe;AACrD,gBAAM,OAAQ,OAAO,iBAAiB,OAAO;AAC7C,gBAAM,YAAY,OAAO;AACzB,gBAAM,cAAc,OAAO;AAC3B,gBAAM,YAAY,cAAc,UAAa,UAAU,SAAS,GAAG;AACnE,gBAAM,QAAwB,CAAC;AAC/B,cAAI,cAAc,UAAa,UAAU,SAAS,GAAG,GAAG;AACtD,kBAAM,KAAK,UAAU;AAAA,UACvB;AACA,cAAI,cAAc,UAAa,UAAU,SAAS,GAAG,GAAG;AACtD,kBAAM,KAAK,QAAQ;AAAA,UACrB;AACA,gBAAM,WAAW,cACb,mBAAmB,WAAW,IAC9B;AACJ,gBAAM,cAAwB;AAAA,YAC5B;AAAA,UACF;AACA,cAAI,UAAU;AACZ,wBAAY,WAAW;AAAA,UACzB;AACA,cAAI,MAAM,SAAS,GAAG;AACpB,wBAAY,QAAQ;AAAA,UACtB;AAGA,gBAAM,YAAY;AAAA,YAChB,KAAK;AAAA,YACL;AAAA,YACA;AAAA,UACF;AAGA,gBAAM,UAAwB;AAAA,YAC5B,MAAM;AAAA,YACN,OAAO;AAAA,UACT;AACA,cAAI,UAAU;AACZ,oBAAQ,WAAW;AAAA,UACrB;AACA,gBAAM,KAAK,OAAO;AAAA,QACpB,WAES,OAAO,mBAAmB;AACjC,eAAK,wBAAwB,QAAQ,KAAK;AAAA,QAC5C,OAEK;AACH,gBAAM,cAAc,OAAO,cAAe,KAAK;AAC/C,gBAAM,QAAQ,OAAO,aAAa,IAAI,KAAK;AAC3C,cAAI,CAAC,MAAM;AACT,kBAAM,IAAI,MAAM,oBAAoB;AAAA,UACtC;AACA,gBAAM,OAAO,OAAO,aAAa;AACjC,gBAAM,WAAW,mBAAmB,WAAW;AAC/C,gBAAM,WAAkB;AAAA,YACtB;AAAA,YACA;AAAA,YACA;AAAA,UACF;AACA,gBAAM,KAAK,EAAE,MAAM,SAAS,OAAO,KAAK,OAAO,KAAK,QAAQ,IAAI,EAAE,CAAC;AAAA,QACrE;AAEA,iBAAS,MAAM,MAAM,CAAC,EAAE;AAAA,MAC1B;AAEA,UAAI,SAAS,KAAK,QAAQ;AACxB,cAAM,KAAK,EAAE,MAAM,QAAQ,OAAO,KAAK,MAAM,MAAM,EAAE,CAAC;AAAA,MACxD;AAEA,wBAAkB;AAAA,IACpB;AAGA,sBAAkB,SAAS,KAAK;AAChC,qBAAiB,SAAS,WAAW,KAAK,eAAe,QAAQ,IAAI,CAAC,CAAC;AACvE,QAAI,CAAC,QAAQ,QAAQ,GAAG;AACtB,WAAK,SAAS,KAAK,OAAO;AAAA,IAC5B;AAEA,SAAK,gCAAgC;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,QAAQ,aAA6B;AACnC,QAAI,mBAAmB,KAAK,YAAY;AAGxC,QAAI,qBAAqB,UAAa,qBAAqB,GAAG;AAC5D,yBAAmB;AAAA,IACrB;AAEA,UAAM,aAAS,YAAAC,SAAI,WAAW,EAAE,IAAI,gBAAgB;AACpD,WAAO,KAAK,QAAQ,MAAM;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,QAAQ,QAA8B;AACpC,UAAM,YAAY,KAAK,MAAM;AAE7B,QAAI,mBAAmB,UAAU,YAAY;AAG7C,QAAI,qBAAqB,UAAa,qBAAqB,GAAG;AAC5D,yBAAmB;AAAA,IACrB;AAGA,UAAM,aAAa,KAAK;AAExB,aAAS,oBACP,cACAC,SACA;AACA,iBAAW,eAAe,cAAc;AACtC,YAAI,YAAY,UAAU;AACxB,gBAAM,cAAc,YAAY,eAAW,YAAAD,SAAIC,OAAM,IAAI;AAEzD,cACE,YAAY,SAAS,SAAS,WAC9B,YAAY,SAAS,MAAM,SAAS,QACpC;AACA,wBAAY,WAAW;AAAA,cACrB,YAAY;AAAA,cACZ;AAAA,YACF;AAAA,UACF;AAEA,cAAI,YAAY,aAAa;AAC3B,wBAAY,cAAc,YAAY,YAAY;AAAA,cAChD,CAAC,gBAA0C;AACzC,oBACE,YAAY,SAAS,SAAS,WAC9B,YAAY,SAAS,MAAM,SAAS,QACpC;AACA,yBAAO;AAAA,gBACT,OAAO;AACL,yBAAO;AAAA,oBACL,GAAG;AAAA,oBACH,UAAU;AAAA,sBACR,YAAY;AAAA,sBACZ;AAAA,oBACF;AAAA,kBACF;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAIA,gBAAM,mBAAmB;AAAA,YACvB;AAAA,cACE,UAAU,YAAY;AAAA,cACtB,MAAM,YAAY;AAAA,YACpB;AAAA,YACA;AAAA,UACF;AACA,sBAAY,WAAW,iBAAiB;AACxC,sBAAY,OAAO,iBAAiB;AAGpC,cAAI,YAAY,aAAa;AAC3B,wBAAY,cAAc,YAAY,YAAY;AAAA,cAAI,CAAC,OACrD,cAAc,IAAI,UAAU;AAAA,YAC9B;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,eAAW,WAAW,UAAU,UAAU;AACxC,iBAAW,QAAQ,QAAQ,QAAQ;AAAA,QACjC,CAAC,SAAS,KAAK,SAAS;AAAA,MAC1B,GAAG;AACD,mBAAW,QAAQ,KAAK,MAAM;AAAA,UAC5B,CAACF,UAASA,MAAK,SAAS;AAAA,QAC1B,GAAG;AACD,8BAAoB,KAAK,cAAc,MAAM;AAAA,QAC/C;AAAA,MACF;AAAA,IACF;AAGA,eAAW,gBAAgB,UAAU,QAAQ,iBAAiB,OAAO,GAAG;AACtE,0BAAoB,cAAc,MAAM;AAAA,IAC1C;AACA,eAAW,gBAAgB,UAAU,QAAQ,gBAAgB,OAAO,GAAG;AACrE,0BAAoB,cAAc,MAAM;AAAA,IAC1C;AAGA,eAAW,aAAa,UAAU,aAAa;AAC7C,gBAAU,WAAW;AAAA,QACnB,UAAU;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAEA,cAAU,gCAAgC;AAE1C,cAAU,eAAW,YAAAC,SAAI,gBAAgB,EAAE,MAAM,MAAM,EAAE,SAAS;AAGlE,QAAI,UAAU,SAAS,YAAY,KAAK,SAAS,UAAU;AACzD,UACE,WAAW,KAAK,OAAO,KAAK,SAAS,QAAQ,EAAE,QAAQ,KAAK,GAAG,EAAE,KAAK,CAAC,GACvE;AACA,cAAM,gBAAgB;AAAA,UACpB,OAAO,KAAK,SAAS,QAAQ,EAAE,QAAQ,KAAK,GAAG;AAAA,QACjD;AACA,kBAAU,SAAS,WAAW;AAAA,cAC5B,YAAAA,SAAI,aAAa,EAAE,MAAM,MAAM,EAAE,SAAS;AAAA,QAC5C;AAAA,MACF;AAAA,IACF;AAGA,QAAI,UAAU,SAAS,SAAS,KAAK,SAAS,OAAO;AACnD,UACE,WAAW,KAAK,OAAO,KAAK,SAAS,KAAK,EAAE,QAAQ,KAAK,GAAG,EAAE,KAAK,CAAC,GACpE;AACA,cAAM,aAAa;AAAA,UACjB,OAAO,KAAK,SAAS,KAAK,EAAE,QAAQ,KAAK,GAAG;AAAA,QAC9C;AACA,kBAAU,SAAS,QAAQ;AAAA,cACzB,YAAAA,SAAI,UAAU,EAAE,MAAM,MAAM,EAAE,SAAS;AAAA,QACzC;AAAA,MACF;AAAA,IACF;AAGA,QAAI,UAAU,SAAS,UAAU,KAAK,SAAS,QAAQ;AACrD,UACE,WAAW,KAAK,OAAO,KAAK,SAAS,MAAM,EAAE,QAAQ,KAAK,GAAG,EAAE,KAAK,CAAC,GACrE;AACA,cAAM,cAAc;AAAA,UAClB,OAAO,KAAK,SAAS,MAAM,EAAE,QAAQ,KAAK,GAAG;AAAA,QAC/C;AACA,kBAAU,SAAS,SAAS;AAAA,cAC1B,YAAAA,SAAI,WAAW,EAAE,MAAM,MAAM,EAAE,SAAS;AAAA,QAC1C;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,UACE,QACA,QACQ;AACR,UAAM,YAAY,KAAK,MAAM;AAK7B,aAAS,gBACP,cACA,YACA,sBACA,UACA,kBACA,QACuB;AACvB,YAAM,UACJ,oBAAoB,aAAa,OAC7B,EAAE,MAAM,aAAa,KAAK,MAAM,kBAAkB,KAAK,IACvD,aAAa;AAEnB,YAAM,aAAoC;AAAA,QACxC,UAAU,aAAa;AAAA,QACvB,MAAM;AAAA,QACN;AAAA,MACF;AAEA,UAAI,WAAW,UAAU;AACvB,eAAO;AAAA,MACT,WAAW,WAAW,WAAW;AAC/B,YAAI,qBAAqB,SAAS,GAAG;AAEnC,qBAAW,cAAc;AAEzB,cAAI,WAAW,YAAa,YAAW,YAAY,KAAK,UAAU;AAAA,QACpE;AAAA,MACF,OAAO;AAEL,mBAAW,cAAc,CAAC,YAAY,GAAG,oBAAoB;AAAA,MAC/D;AAEA,aAAO;AAAA,IACT;AAKA,aAAS,2BACP,aACuB;AACvB,YAAM,cAAc,YAAY,YAAY,MAAM,IAAI;AACtD,YAAM,cAAc,YAAY,eAAe,CAAC;AAChD,YAAM,aAAuC;AAAA,QAC3C,UAAU,YAAY;AAAA,QACtB,MAAM,YAAY;AAAA,MACpB;AAGA,UACE,YAAY,SAAS,WACrB,2BAA2B,aAAa,MAAM,GAC9C;AAEA,YAAI,WAAW,UAAU;AACvB,iBAAO;AAAA,YACL,UAAU,YAAY;AAAA,YACtB,MAAM,YAAY;AAAA,YAClB,UAAU,YAAY;AAAA,UACxB;AAAA,QACF;AACA,eAAO;AAAA,UACL,UAAU,YAAY;AAAA,UACtB,MAAM,YAAY;AAAA,UAClB,UAAU,YAAY;AAAA,UACtB;AAAA,QACF;AAAA,MACF;AAGA,YAAM,mBAAmB,YAAY,UAAU,CAAC,OAAO;AACrD,cAAM,SAAS,YAAY,GAAG,MAAM,IAAI;AACxC,eACE,OAAO,SAAS,WAAW,2BAA2B,QAAQ,MAAM;AAAA,MAExE,CAAC;AAED,UAAI,qBAAqB,IAAI;AAE3B,cAAM,cAAc,YAAY,gBAAgB;AAChD,cAAM,uBAAuB,YAAY;AAAA,UACvC,CAAC,GAAGF,OAAMA,OAAM;AAAA,QAClB;AACA,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA;AAAA,UACA,YAAY;AAAA,UACZ,YAAY,MAAM;AAAA,UAClB;AAAA,QACF;AAAA,MACF;AAGA,YAAM,YAAY,wBAAwB,YAAY,MAAM;AAE5D,UAAI,aAAa,UAAU,MAAM;AAC/B,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA;AAAA,UACA,YAAY;AAAA,UACZ,YAAY,MAAM;AAAA,UAClB;AAAA,QACF;AAAA,MACF;AAGA,eAASA,KAAI,GAAGA,KAAI,YAAY,QAAQA,MAAK;AAC3C,cAAM,QAAQ,YAAYA,EAAC;AAC3B,cAAM,iBAAiB,wBAAwB,OAAO,MAAM;AAG5D,YAAI,kBAAkB,eAAe,MAAM;AACzC,gBAAM,uBACJ,WAAW,SACP,cACA,YAAY,OAAO,CAAC,GAAG,QAAQ,QAAQA,EAAC;AAC9C,iBAAO;AAAA,YACL;AAAA,YACA;AAAA,YACA;AAAA,YACA,YAAY;AAAA,YACZ,MAAM,MAAM;AAAA,YACZ;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAIA,UAAI,WAAW,UAAU;AACvB,eAAO;AAAA,UACL,UAAU,YAAY;AAAA,UACtB,MAAM,YAAY;AAAA,UAClB,UAAU,YAAY;AAAA,QACxB;AAAA,MACF,OAAO;AACL,eAAO;AAAA,UACL,UAAU,YAAY;AAAA,UACtB,MAAM,YAAY;AAAA,UAClB,UAAU,YAAY;AAAA,UACtB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAKA,aAAS,oBAAoB,cAAuC;AAClE,iBAAW,eAAe,cAAc;AAEtC,YAAI,YAAY,UAAU;AACxB,gBAAM,YAAY;AAAA,YAChB;AAAA,UACF;AACA,sBAAY,WAAW,UAAU;AACjC,sBAAY,OAAO,UAAU;AAC7B,UACE,YACA,WAAW,UAAU;AACvB,sBAAY,cAAc,UAAU;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAGA,eAAW,WAAW,UAAU,UAAU;AACxC,iBAAW,QAAQ,QAAQ,QAAQ;AAAA,QACjC,CAAC,SAAS,KAAK,SAAS;AAAA,MAC1B,GAAG;AACD,mBAAW,QAAQ,KAAK,MAAM;AAAA,UAC5B,CAACC,UAASA,MAAK,SAAS;AAAA,QAC1B,GAAG;AACD,8BAAoB,KAAK,YAAY;AAAA,QACvC;AAAA,MACF;AAAA,IACF;AAGA,eAAW,gBAAgB,UAAU,QAAQ,iBAAiB,OAAO,GAAG;AACtE,0BAAoB,YAAY;AAAA,IAClC;AACA,eAAW,gBAAgB,UAAU,QAAQ,gBAAgB,OAAO,GAAG;AACrE,0BAAoB,YAAY;AAAA,IAClC;AAGA,cAAU,gCAAgC;AAI1C,QAAI,WAAW,OAAQ,SAAO,YAAY,IAAI,WAAW,MAAM;AAE/D,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,cAAkC;AACxC,QAAI,KAAK,UAAU;AACjB,aAAO,KAAK;AAAA,IACd;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAgB;AACd,UAAM,YAAY,IAAI,QAAO;AAC7B,cAAU,UAAU,UAAU,KAAK,OAAO;AAC1C,YAAO,WAAW,IAAI,WAAW,KAAK,aAAa,CAAC;AAEpD,cAAU,WAAW,UAAU,KAAK,QAAQ;AAC5C,cAAU,cAAc,UAAU,KAAK,WAAW;AAClD,cAAU,WAAW,KAAK,SAAS,IAAI,CAAC,YAAY;AAClD,YAAM,aAAa,IAAI,QAAQ,QAAQ,IAAI;AAC3C,iBAAW,UAAU,UAAU,QAAQ,OAAO;AAC9C,aAAO;AAAA,IACT,CAAC;AACD,cAAU,WAAW,UAAU,KAAK,QAAQ;AAC5C,cAAU,SAAS,UAAU,KAAK,MAAM;AACxC,cAAU,cAAc,UAAU,KAAK,WAAW;AAClD,cAAU,WAAW,KAAK;AAC1B,WAAO;AAAA,EACT;AACF;AAAA;AAAA;AAAA;AAAA;AAh4CE,cAvDW,SAuDI,eAAc,oBAAI,QAAoC;AAAA;AAAA;AAAA;AAAA;AAMrE,cA7DW,SA6DI,cAAa,oBAAI,QAAwB;AA7DnD,IAAM,SAAN;;;ACzDA,IAAM,eAAN,MAAmB;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBxB,YAAY,qBAA+C;AAlB3D;AAAA;AAAA;AAAA;AAAA,uCAAiC,CAAC;AAIlC;AAAA;AAAA;AAAA,mCAAyB,CAAC;AAI1B;AAAA;AAAA;AAAA;AAIA;AAAA;AAAA;AAAA;AAOE,QAAI,qBAAqB;AACvB,WAAK,oBAAoB,mBAAmB;AAAA,IAC9C;AAAA,EACF;AAAA,EAEQ,wBAAwB;AAC9B,SAAK,cAAc,CAAC;AAEpB,UAAM,wBAAwB,CAC5B,MACA,kBAGG;AACH,YAAM,wBAAwB,eAAe,aAAa;AAC1D,YAAM,gBACJ,WAAW,qBAAqB,IAC5B,sBAAsB,MACtB,CAAC,qBAAqB;AAE5B,YAAM,WAAW,KAAK,YAAY,KAAK,CAACG,OAAMA,GAAE,SAAS,IAAI;AAE7D,UAAI,UAAU;AACZ,YAAI,CAAC,SAAS,eAAe;AAC3B,mBAAS,gBAAgB;AACzB;AAAA,QACF;AACA,YAAI;AACF,gBAAM,gCAAgC;AAAA,YACpC,SAAS;AAAA,UACX;AACA,gBAAM,qBACJ,WAAW,6BAA6B,IACpC,8BAA8B,MAC9B,CAAC,6BAA6B;AAKpC,mBAAS,gBAAgB,0BAA0B;AAAA,YACjD,GAAG;AAAA,YACH,GAAG;AAAA,UACL,CAAC;AACD;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAEA,WAAK,YAAY,KAAK;AAAA,QACpB;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAEA,eAAW,eAAe,KAAK,SAAS;AACtC,UAAI;AACJ,UAAI,YAAY,aAAa;AAC3B,cAAM,EAAE,QAAQ,OAAO,IAAI;AAC3B,uBAAe,WAAW,IAAI,SAAS,OAAO,QAAQ,MAAM;AAAA,MAC9D,OAAO;AACL,uBAAe,YAAY,OAAO,QAAQ,YAAY,QAAQ;AAAA,MAChE;AAGA,YAAM,cAAc,aAAa,wBAAwB;AAAA,QACvD,SAAS,YAAY;AAAA,MACvB,CAAC;AAED,iBAAW,cAAc,aAAa;AAEpC,YAAI,WAAW,SAAS,WAAW,MAAM,SAAS,QAAQ,GAAG;AAC3D;AAAA,QACF;AAIA,YAAI,CAAC,WAAW,eAAe;AAC7B;AAAA,QACF;AAGA,YAAI,WAAW,cAAc,WAAW,WAAW,SAAS,GAAG;AAE7D,gBAAM,gBAGA,CAAC;AACP,qBAAW,UAAU,WAAW,YAAY;AAC1C,gBAAI,SAAS,QAAQ;AAEnB,yBAAW,OAAO,OAAO,KAAK;AAC5B,8BAAc,KAAK,GAAG;AAAA,cACxB;AAAA,YACF,OAAO;AAEL,oBAAM,WAAkC;AAAA,gBACtC,UAAU,OAAO;AAAA,cACnB;AACA,kBAAI,OAAO,KAAM,UAAS,OAAO,OAAO;AACxC,kBAAI,OAAO,YAAa,UAAS,cAAc,OAAO;AACtD,4BAAc,KAAK,QAAQ;AAAA,YAC7B;AAAA,UACF;AACA,cAAI,cAAc,WAAW,GAAG;AAC9B,kCAAsB,WAAW,MAAM,cAAc,CAAC,CAAE;AAAA,UAC1D,OAAO;AAGL,kBAAM,qBAAqB,cAAc;AAAA,cAAI,CAAC,MAC5C,eAAe,CAAC;AAAA,YAClB;AACA,kBAAM,gBAAgB;AAAA,cACpB;AAAA,YAIF;AAEA,kCAAsB,WAAW,MAAM,aAAa;AAAA,UACtD;AAAA,QACF,WAAW,CAAC,KAAK,YAAY,KAAK,CAACA,OAAMA,GAAE,SAAS,WAAW,IAAI,GAAG;AACpE,eAAK,YAAY,KAAK,EAAE,MAAM,WAAW,KAAK,CAAC;AAAA,QACjD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,WAAW,QAAgB,UAA8B,CAAC,GAAS;AAEjE,UAAM,eAAe,KAAK;AAAA,MACxB;AAAA,MACA,QAAQ;AAAA,IACV;AACA,QAAI,cAAc;AAChB,YAAM,IAAI,MAAM,YAAY;AAAA,IAC9B;AAEA,QAAI,CAAC,QAAQ,SAAS;AACpB,WAAK,QAAQ,KAAK;AAAA,QAChB;AAAA,QACA,QAAQ,QAAQ,WAAW;AAAA,QAC3B,SAAS,QAAQ;AAAA,MACnB,CAAC;AAAA,IACH,OAAO;AACL,UAAI,YAAY,QAAQ,SAAS;AAC/B,aAAK,QAAQ,KAAK;AAAA,UAChB;AAAA,UACA,QAAQ,QAAQ,QAAQ;AAAA,UACxB,SAAS,QAAQ;AAAA,QACnB,CAAC;AAAA,MACH,OAAO;AACL,aAAK,QAAQ,KAAK;AAAA,UAChB;AAAA,UACA,UAAU,QAAQ,QAAQ;AAAA,UAC1B,SAAS,QAAQ;AAAA,QACnB,CAAC;AAAA,MACH;AAAA,IACF;AACA,SAAK,sBAAsB;AAC3B,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,+BACN,QACA,SACoB;AACpB,UAAM,eAAyB,CAAC;AAChC,UAAM,gBAA0B,CAAC;AAGjC,eAAW,UAAU,OAAO,QAAQ,gBAAgB,KAAK,GAAG;AAC1D,UAAI,CAAC,SAAS,iBAAiB,IAAI,MAAM,GAAG;AAC1C,qBAAa,KAAK,MAAM;AAAA,MAC1B;AAAA,IACF;AAGA,eAAW,WAAW,OAAO,QAAQ,iBAAiB,KAAK,GAAG;AAC5D,UAAI,CAAC,SAAS,kBAAkB,IAAI,OAAO,GAAG;AAC5C,sBAAc,KAAK,OAAO;AAAA,MAC5B;AAAA,IACF;AAEA,QAAI,aAAa,WAAW,KAAK,cAAc,WAAW,GAAG;AAC3D,aAAO;AAAA,IACT;AAEA,UAAM,QAAkB,CAAC;AACzB,QAAI,aAAa,SAAS,GAAG;AAC3B,YAAM;AAAA,QACJ,qBAAqB,aAAa,IAAI,CAACA,OAAM,IAAIA,EAAC,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA,MACnE;AAAA,IACF;AACA,QAAI,cAAc,SAAS,GAAG;AAC5B,YAAM;AAAA,QACJ,sBAAsB,cAAc,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA,MACrE;AAAA,IACF;AACA,WAAO,4DAA4D,MAAM,KAAK,IAAI,CAAC;AAAA,EACrF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAc,OAAe;AAC3B,QAAI,QAAQ,KAAK,SAAS,KAAK,QAAQ,QAAQ;AAC7C,YAAM,IAAI,MAAM,qBAAqB;AAAA,IACvC;AACA,SAAK,QAAQ,OAAO,OAAO,CAAC;AAC5B,SAAK,sBAAsB;AAC3B,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAAoB,QAAiC;AACnD,QAAI,OAAO,WAAW;AACpB,WAAK,kBAAkB,IAAI,eAAe,MAAM;AAAA,aACzC,kBAAkB,eAAgB,MAAK,kBAAkB;AAAA,QAC7D,OAAM,IAAI,MAAM,gCAAgC;AACrD,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa;AACX,QAAI,CAAC,KAAK,iBAAiB;AACzB,WAAK,aAAa,EAAE,OAAO,KAAK,YAAY;AAC5C;AAAA,IACF;AAEA,UAAM,aAAqC,EAAE,OAAO,CAAC,EAAE;AACvD,eAAW,YAAY,KAAK,gBAAgB,YAAY;AACtD,iBAAW,SAAS,IAAI,IAAI,CAAC;AAAA,IAC/B;AAEA,eAAW,cAAc,KAAK,aAAa;AACzC,UAAI,QAAQ;AACZ,iBAAW,YAAY,KAAK,gBAAgB,YAAY;AACtD,mBAAW,sBAAsB,SAAS,aAAa;AACrD,cAAI,mBAAmB,QAAQ,SAAS,WAAW,IAAI,GAAG;AACxD,uBAAW,SAAS,IAAI,EAAG,KAAK,UAAU;AAC1C,oBAAQ;AACR;AAAA,UACF;AAAA,QACF;AACA,YAAI,OAAO;AACT;AAAA,QACF;AAAA,MACF;AACA,UAAI,CAAC,OAAO;AACV,mBAAW,MAAO,KAAK,UAAU;AAAA,MACnC;AAAA,IACF;AAEA,SAAK,aAAa;AAAA,EACpB;AACF;;;AC5TA,mBAAkC;AAmE3B,IAAM,eAAN,MAAmB;AAAA;AAAA;AAAA;AAAA;AAAA,EA8BxB,YAAY,SAA+B;AA1B3C;AAAA;AAAA;AAAA;AAIA;AAAA;AAAA;AAAA;AAIA;AAAA;AAAA;AAAA,gCAAoB,CAAC;AAIrB;AAAA;AAAA;AAAA,iCAAmB,CAAC;AAIpB;AAAA;AAAA;AAAA,oCAAyB,CAAC;AAI1B;AAAA;AAAA;AAAA;AAOE,QAAI,SAAS,QAAS,MAAK,iBAAiB,QAAQ;AACpD,QAAI,SAAS,KAAM,MAAK,eAAe,QAAQ;AAC/C,SAAK,UAAU,EAAE,YAAY,GAAG,YAAY,EAAE;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAkB,SAAyB;AACzC,SAAK,iBAAiB;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,gBAAgB,MAAoB;AAClC,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,YAAqB;AACnB,SAAK,UAAU;AAEf,QAAI,KAAK,mBAAmB,QAAW;AACrC,YAAM,IAAI,6BAA6B;AAAA,IACzC,WAAW,KAAK,iBAAiB,QAAW;AAC1C,YAAM,IAAI,2BAA2B;AAAA,IACvC;AAEA,eAAW,cAAc,KAAK,aAAa,aAAa;AACtD,YAAM,iBAAiB,KAAK,kBAAkB,UAAU;AACxD,UAAI;AACF,cAAM,eAAe,KAAK,gBAAgB,YAAY,cAAc;AACpE,aAAK,KAAK,KAAK,GAAG,YAAY;AAC9B,aAAK,MAAM,KAAK,EAAE,YAAY,WAAW,aAAa,CAAC;AAAA,MACzD,SAAS,OAAO;AAEd,YAAI,iBAAiB,qBAAqB;AACxC,eAAK,SAAS,KAAK,EAAE,YAAY,QAAQ,MAAM,KAAK,CAAC;AAAA,QACvD;AAAA,MACF;AAAA,IACF;AAEA,SAAK,UAAU;AAEf,WAAO,KAAK,SAAS,SAAS;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,kBAAkB,YAA8C;AAEtE,WAAO,KAAK,eAAgB,SAAS;AAAA,MACnC,CAAC,YACC,QAAQ,mBAAmB,WAAW,QACtC,QAAQ,mBAAmB,SAAS,WAAW,IAAI;AAAA,IACvD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,gBACN,YACA,SACoB;AAEpB,QAAI,QAAQ,WAAW;AACrB,YAAM,IAAI,oBAAoB,WAAW,MAAM,WAAW;AAE5D,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,oBAAoB,WAAW,MAAM,YAAY;AAG7D,UAAM,oBAA+C,QAAQ;AAAA,MAC3D,CAAC,YAAY;AAAA,QACX,GAAG;AAAA,QACH,OAAO,OAAO,MAAM,IAAI,CAAC,MAA6B;AACpD,gBAAM,eAAe,YAAY,EAAE,IAAI;AACvC,iBAAO;AAAA,YACL,MACE,gBAAgB,YAAY,eACvB;AAAA,cACC,EAAE;AAAA,cACF,aAAa;AAAA,YACf,IACA,EAAE;AAAA,YACR,MAAM;AAAA,UACR;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AACA,UAAM,0BAA0B,kBAAkB,WAAW,aAAa;AAE1E,aAAS,gCACP,sBAGAC,oBACA,YAAgC,CAAC,GACb;AACpB,UAAI,WAAW,oBAAoB,GAAG;AACpC,mBAAW,KAAK,qBAAqB,KAAK;AACxC,gBAAM,SAAS;AAAA,YACb;AAAA,YACAA;AAAA,YACA;AAAA,UACF;AACA,oBAAU,KAAK,GAAG,MAAM;AAAA,QAC1B;AAAA,MACF,OAAO;AACL,cAAM,6BAA6B,UAAU,oBAAoB,IAC5D,qBAA0D,KAC3D,CAAC,oBAAoB;AACzB,cAAM,YAAkC,CAAC;AACzC,cAAM,SAAS,oBAAI,IAA6B;AAChD,mBAAW,eAAe,4BAA4B;AAEpD,cACE,YAAY,SAAS,SAAS,WAC9B,YAAY,SAAS,MAAM,SAAS,QACpC;AACA,mBAAO,IAAI,WAAW;AACtB;AAAA,UACF;AAGA,gBAAM,iBAAiB;AAAA,YACrB,YAAY;AAAA,YACZ,YAAY,YAAY,OAAO,YAAY,KAAK,SAAS;AAAA,UAC3D;AACA,sBAAY,WAAW;AAGvB,gBAAM,eAAeA,mBAAkB;AAAA,YAAO,CAAC,WAC7C,OAAO,MAAM;AAAA,cAAK,CAAC,MACjB,kBAAkB,YAAY,MAAM,EAAE,IAAI;AAAA,YAC5C;AAAA,UACF;AACA,cAAI,aAAa,SAAS,GAAG;AAE3B,kBAAM,qBAAqB,CACzB,WAEA,OAAO,MAAM;AAAA,cAAK,CAAC,MACjB,kBAAkB,YAAY,MAAM,EAAE,IAAI;AAAA,YAC5C;AAGF,gBAAI,aAAa,UAAU,GAAG;AAC5B,oBAAM,gBAAgB,aAAa,CAAC;AACpC,oBAAM,iBAAiB,mBAAmB,aAAa;AACvD,oBAAM,UAAU,QAAQ;AAAA,gBACtB,CAAC,QAAQ,IAAI,OAAO,cAAc;AAAA,cACpC;AAEA,oBAAM,iBACJ,eAAe,SAAS,UACpB,eAAe,QACf,eAAe;AACrB,oBAAM,cAAc,KAAK;AAAA,gBACvB,gBAAgB,cAAc,IAC5B,gBAAgB,eAAe,KAAK,KAAK;AAAA,cAC7C;AACA,wBAAU,KAAK;AAAA,gBACb;AAAA,kBACE;AAAA,kBACA,UAAU;AAAA,kBACV,YAAY,cAAc,cAAc;AAAA,gBAC1C;AAAA,cACF,CAAC;AACD;AAAA,YACF;AAGA,kBAAM,QAAe;AAAA,cACnB,WAAW;AAAA,cACX,WAAW;AAAA,cACX,UAAU;AAAA,cACV,aAAa;AAAA,gBACX,MAAM;AAAA,kBACJ,KACE,eAAe,SAAS,UACpB,gBAAgB,eAAe,KAAK,IACpC,gBAAgB,eAAe,GAAG;AAAA,gBAC1C;AAAA,cACF;AAAA,cACA,WAAW,aAAa;AAAA,gBACtB,CAAC,KAAK,WAAW;AACf,wBAAM,iBAAiB,mBAAmB,MAAM;AAChD,sBAAI,OAAO,EAAE,IAAI;AAAA,oBACf,OAAO,OAAO;AAAA,oBACd,MAAM,gBAAgB,eAAe,KAAK,KAAK;AAAA,kBACjD;AACA,yBAAO;AAAA,gBACT;AAAA,gBACA,CAAC;AAAA,cACH;AAAA,YACF;AAEA,kBAAM,eAAW,oBAAM,KAAK;AAC5B,sBAAU;AAAA,cACR,SAAS,UAAU,IAAI,CAAC,aAAa;AACnC,sBAAM,sBAAsB;AAAA,kBAC1B,SAAS,QAAQ,KAAK,CAAC,WAAW,OAAO,OAAO,SAAS,CAAC,CAAC;AAAA,kBAC3D,UAAU,SAAS,CAAC;AAAA,gBACtB;AACA,uBAAO;AAAA,kBACL,GAAG;AAAA,kBACH,YACE,oBAAoB,WACpB,oBAAoB,QAAQ;AAAA,gBAChC;AAAA,cACF,CAAC;AAAA,YACH;AAAA,UACF,OAAO;AACL,mBAAO,IAAI,mBAAmB;AAAA,UAChC;AAAA,QACF;AAEA,YAAI,UAAU,WAAW,GAAG;AAC1B,gBAAM,IAAI;AAAA,YACR,WAAW;AAAA,YACX,OAAO,SAAS,IACX,OAAO,OAAO,EAAE,KAAK,EAAE,QACxB;AAAA,UACN;AAAA,QACF,OAAO;AAEL,iBAAO,UAAU;AAAA,YACf,CAACC,IAAG,MACFA,GAAE,OAAO,CAAC,KAAK,SAAS,MAAM,KAAK,YAAY,CAAC,IAChD,EAAE,OAAO,CAAC,KAAK,SAAS,MAAM,KAAK,YAAY,CAAC;AAAA,UACpD,EAAE,CAAC;AAAA,QACL;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAY;AAClB,SAAK,OAAO,CAAC;AACb,SAAK,QAAQ,CAAC;AACd,SAAK,WAAW,CAAC;AACjB,SAAK,UAAU,EAAE,YAAY,GAAG,YAAY,EAAE;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAiC;AAC/B,SAAK,QAAQ,aAAa,KAAK,KAAK;AAAA,MAClC,CAAC,KAAK,SAAS,MAAM,KAAK;AAAA,MAC1B;AAAA,IACF;AACA,SAAK,QAAQ,aAAa,KAAK,KAAK;AACpC,WAAO,KAAK;AAAA,EACd;AACF;;;AC7YA,IAAM,mBAA2C;AAAA,EAC/C,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACT;AAoBO,SAAS,uBAAuB,KAAa,KAAqB;AAEvE,QAAM,YAAY,KAAK,MAAM,MAAM,GAAG;AACtC,QAAM,YAAY,MAAM;AAExB,MAAI,cAAc,GAAG;AAEnB,WAAO,OAAO,SAAS;AAAA,EACzB;AAEA,QAAM,cAAc,GAAG,SAAS,IAAI,GAAG;AACvC,QAAM,SAAS,iBAAiB,WAAW;AAE3C,MAAI,YAAY,GAAG;AAEjB,WAAO,SACH,GAAG,SAAS,GAAG,MAAM,KACrB,GAAG,SAAS,IAAI,SAAS,IAAI,GAAG;AAAA,EACtC;AAGA,SAAO,UAAU,GAAG,GAAG,IAAI,GAAG;AAChC;AAkBO,SAAS,mBACd,OACA,YAAqB,MACb;AACR,MAAI,MAAM,SAAS,WAAW;AAC5B,WAAO,OAAO,MAAM,OAAO;AAAA,EAC7B;AACA,MAAI,WAAW;AACb,WAAO,uBAAuB,MAAM,KAAK,MAAM,GAAG;AAAA,EACpD;AACA,SAAO,GAAG,MAAM,GAAG,IAAI,MAAM,GAAG;AAClC;AAgBO,SAAS,kBACd,OACQ;AACR,MAAI,MAAM,SAAS,QAAQ;AACzB,WAAO,MAAM;AAAA,EACf;AACA,SAAO,mBAAmB,KAAK;AACjC;AAeO,SAAS,eAAe,UAAsC;AACnE,MAAI,SAAS,SAAS,SAAS;AAC7B,WAAO,kBAAkB,SAAS,KAAK;AAAA,EACzC;AAEA,QAAM,SAAS,mBAAmB,SAAS,GAAG;AAC9C,QAAM,SAAS,mBAAmB,SAAS,GAAG;AAC9C,SAAO,GAAG,MAAM,IAAI,MAAM;AAC5B;AAgBO,SAAS,WAAW,MAAyC;AAClE,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,SAAO,KAAK;AACd;AAgBO,SAAS,uBACd,UACA,MACQ;AACR,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,MAAM,eAAe,QAAQ;AACnC,QAAM,UAAU,WAAW,IAAI;AAC/B,SAAO,UAAU,GAAG,GAAG,IAAI,OAAO,KAAK;AACzC;AASO,SAAS,uBAAuB,MAAwC;AAC7E,SAAO,uBAAuB,KAAK,UAAU,KAAK,IAAI;AACxD;AAiBO,SAAS,mBACd,cACA,YAAoB,OACZ;AACR,QAAM,QAAkB,CAAC;AAGzB,QAAM,KAAK,uBAAuB,YAAY,CAAC;AAG/C,MAAI,aAAa,aAAa;AAC5B,eAAW,MAAM,aAAa,aAAa;AACzC,YAAM,KAAK,uBAAuB,EAAE,CAAC;AAAA,IACvC;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,SAAS;AAC7B;AA6BO,SAAS,cAAc,MAA+B;AAC3D,SAAO,KAAK,UAAU;AACxB;AAgCO,SAAS,sBACd,QACA,SACA,MACA,kBACS;AAET,MAAI,KAAK,OAAO;AAEd,UAAMC,iBAAgB,SAAS,kBAAkB,IAAI,KAAK,KAAK;AAE/D,UAAM,oBAAoB,OAAO,QAAQ,iBAAiB,IAAI,KAAK,KAAK;AACxE,QACE,qBACAA,mBAAkB,UAClBA,iBAAgB,kBAAkB,QAClC;AAEA,YAAM,iBAAiB,kBAAkBA,cAAa,GAAG;AACzD,aAAO,mBAAmB,KAAK;AAAA,IACjC;AACA,WAAO;AAAA,EACT;AAGA,QAAM,gBAAgB,SAAS,iBAAiB,IAAI,KAAK,EAAE;AAC3D,SAAO,qBAAqB;AAC9B;","names":["escapeCache","Map","Flags","GLOBAL","NON_SENSITIVE","MULTILINE","DOT_ALL","UNICODE","STICKY","Ranges","Object","freeze","digit","lowercaseLetter","uppercaseLetter","letter","alphanumeric","anyCharacter","Quantifiers","zeroOrMore","oneOrMore","optional","HumanRegex","constructor","this","parts","flags","Set","add","special","word","whitespace","nonWhitespace","text","has","set","replace","get","or","name","range","Error","chars","lazy","lastPart","pop","newline","pattern","hasSpecialCharacter","hasDigit","hasLetter","n","min","max","startGroup","startCaptureGroup","wordBoundary","nonWordBoundary","endGroup","startAnchor","endAnchor","global","nonSensitive","multiline","dotAll","sticky","variant","validVariants","unicodeDigit","unicodePunctuation","unicodeSymbol","count","length","push","ipv4Octet","protocol","www","tld","path","part","toString","join","toRegExp","RegExp","createRegex","Patterns","createCachedPattern","builder","regex","source","email","literal","atLeast","url","phoneInternational","between","r","import_big","a","Big","a","Big","e","i","e","a","TOML","import_big","units","a","unitRatio","Big","import_big","ing","i","item","Big","factor","i","normalizedOptions","a","selectedIndex"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/classes/category_config.ts","../src/classes/product_catalog.ts","../node_modules/.pnpm/human-regex@2.2.0/node_modules/human-regex/src/human-regex.ts","../src/regex.ts","../src/units/definitions.ts","../src/units/conversion.ts","../src/quantities/numeric.ts","../src/units/compatibility.ts","../src/errors.ts","../src/utils/type_guards.ts","../src/quantities/mutations.ts","../src/utils/parser_helpers.ts","../src/classes/section.ts","../src/quantities/alternatives.ts","../src/units/lookup.ts","../src/utils/general.ts","../src/classes/recipe.ts","../src/classes/shopping_list.ts","../src/classes/shopping_cart.ts","../src/utils/render_helpers.ts"],"sourcesContent":["// Classes\n\nimport { CategoryConfig } from \"./classes/category_config\";\nimport { ProductCatalog } from \"./classes/product_catalog\";\nimport { Recipe } from \"./classes/recipe\";\nimport { ShoppingList } from \"./classes/shopping_list\";\nimport {\n ShoppingCart,\n type ShoppingCartOptions,\n type ShoppingCartSummary,\n} from \"./classes/shopping_cart\";\nimport { Section } from \"./classes/section\";\n\nexport {\n CategoryConfig,\n ProductCatalog,\n Recipe,\n ShoppingList,\n ShoppingCart,\n Section,\n};\n\n// Helpers\n\nimport {\n isAlternativeSelected,\n isGroupedItem,\n renderFractionAsVulgar,\n formatNumericValue,\n formatSingleValue,\n formatQuantity,\n formatUnit,\n formatQuantityWithUnit,\n formatExtendedQuantity,\n formatItemQuantity,\n} from \"./utils/render_helpers\";\nimport {\n isAndGroup,\n isSimpleGroup,\n hasAlternatives,\n} from \"./utils/type_guards\";\nimport { convertQuantityToSystem } from \"./quantities/mutations\";\n\nexport {\n isAlternativeSelected,\n isGroupedItem,\n renderFractionAsVulgar,\n formatNumericValue,\n formatSingleValue,\n formatQuantity,\n formatUnit,\n formatQuantityWithUnit,\n formatExtendedQuantity,\n formatItemQuantity,\n isAndGroup,\n isSimpleGroup,\n hasAlternatives,\n convertQuantityToSystem,\n};\n\n// Types\n\nimport type {\n Metadata,\n MetadataSource,\n MetadataTime,\n MetadataObject,\n MetadataValue,\n Ingredient,\n IngredientFlag,\n IngredientExtras,\n FixedValue,\n Range,\n DecimalValue,\n FractionValue,\n TextValue,\n FixedNumericValue,\n Timer,\n TextItem,\n IngredientItem,\n MaybeScalableQuantity,\n IngredientAlternative,\n CookwareItem,\n TimerItem,\n ArbitraryScalable,\n ArbitraryScalableItem,\n StepItem,\n Step,\n Note,\n NoteItem,\n Cookware,\n CookwareFlag,\n CategorizedIngredients,\n AddedRecipe,\n AddedRecipeOptions,\n AddedIngredient,\n RecipeWithFactor,\n RecipeWithServings,\n CategoryIngredient,\n Category,\n ProductOptionBase,\n ProductOptionCore,\n ProductOption,\n ProductSize,\n ProductSelection,\n CartContent,\n ProductMatch,\n CartMatch,\n ProductMisMatch,\n CartMisMatch,\n NoProductMatchErrorCode,\n QuantityBase,\n QuantityWithPlainUnit,\n QuantityWithExtendedUnit,\n QuantityWithUnitDef,\n QuantityWithUnitLike,\n Unit,\n UnitSystem,\n SpecificUnitSystem,\n ToBaseBySystem,\n UnitType,\n UnitDefinition,\n UnitDefinitionLike,\n AndGroup,\n OrGroup,\n Group,\n FlatAndGroup,\n FlatOrGroup,\n FlatGroup,\n MaybeNestedGroup,\n MaybeNestedAndGroup,\n MaybeNestedOrGroup,\n IngredientQuantityGroup,\n IngredientQuantityAndGroup,\n AlternativeIngredientRef,\n RecipeChoices,\n RecipeAlternatives,\n GetIngredientQuantitiesOptions,\n} from \"./types\";\n\nexport {\n ShoppingCartOptions,\n ShoppingCartSummary,\n Metadata,\n MetadataSource,\n MetadataTime,\n MetadataObject,\n MetadataValue,\n Ingredient,\n IngredientFlag,\n IngredientExtras,\n FixedValue,\n Range,\n DecimalValue,\n FractionValue,\n TextValue,\n FixedNumericValue,\n Timer,\n TextItem,\n IngredientItem,\n MaybeScalableQuantity,\n IngredientAlternative,\n CookwareItem,\n TimerItem,\n ArbitraryScalable,\n ArbitraryScalableItem,\n StepItem,\n Step,\n Note,\n NoteItem,\n Cookware,\n CookwareFlag,\n CategorizedIngredients,\n AddedRecipe,\n AddedRecipeOptions,\n AddedIngredient,\n RecipeWithFactor,\n RecipeWithServings,\n CategoryIngredient,\n Category,\n ProductOptionBase,\n ProductOptionCore,\n ProductOption,\n ProductSize,\n ProductSelection,\n CartContent,\n ProductMatch,\n CartMatch,\n ProductMisMatch,\n CartMisMatch,\n NoProductMatchErrorCode,\n QuantityBase,\n QuantityWithPlainUnit,\n QuantityWithExtendedUnit,\n QuantityWithUnitDef,\n QuantityWithUnitLike,\n Unit,\n UnitSystem,\n SpecificUnitSystem,\n ToBaseBySystem,\n UnitType,\n UnitDefinition,\n UnitDefinitionLike,\n AndGroup,\n OrGroup,\n Group,\n FlatAndGroup,\n FlatOrGroup,\n FlatGroup,\n MaybeNestedGroup,\n MaybeNestedAndGroup,\n MaybeNestedOrGroup,\n IngredientQuantityGroup,\n IngredientQuantityAndGroup,\n AlternativeIngredientRef,\n RecipeChoices,\n RecipeAlternatives,\n GetIngredientQuantitiesOptions,\n};\n\n// Errors\n\nimport {\n NoProductCatalogForCartError,\n NoShoppingListForCartError,\n} from \"./errors\";\n\nexport { NoProductCatalogForCartError, NoShoppingListForCartError };\n","import type { Category, CategoryIngredient } from \"../types\";\n\n/**\n * Parser for category configurations specified à-la-cooklang.\n *\n * ## Usage\n *\n * You can either directly provide the category configuration string when creating the instance\n * e.g. `const categoryConfig = new CategoryConfig(<...>)`, or create it first and then pass\n * the category configuration string to the {@link CategoryConfig.parse | parse()} method.\n *\n * The initialized `CategoryConfig` can then be fed to a {@link ShoppingList}\n *\n * @example\n * ```typescript\n * import { CategoryConfig } from @tmlmt/cooklang-parser;\n *\n * const categoryConfigString = `\n * [Dairy]\n * milk\n * butter\n *\n * [Bakery]\n * flour\n * sugar`;\n *\n * const categoryConfig = new CategoryConfig(categoryConfigString);\n * ```\n *\n * @see [Category Configuration](https://cooklang.org/docs/spec/#shopping-lists) section of the cooklang specs\n *\n * @category Classes\n */\nexport class CategoryConfig {\n /**\n * The parsed categories of ingredients.\n */\n categories: Category[] = [];\n\n /**\n * Creates a new CategoryConfig instance.\n * @param config - The category configuration to parse.\n */\n constructor(config?: string) {\n if (config) {\n this.parse(config);\n }\n }\n\n /**\n * Parses a category configuration from a string into property\n * {@link CategoryConfig.categories | categories}\n * @param config - The category configuration to parse.\n */\n parse(config: string) {\n let currentCategory: Category | null = null;\n const categoryNames = new Set<string>();\n const ingredientNames = new Set<string>();\n\n for (const line of config.split(\"\\n\")) {\n const trimmedLine = line.trim();\n\n if (trimmedLine.length === 0) {\n continue;\n }\n\n if (trimmedLine.startsWith(\"[\") && trimmedLine.endsWith(\"]\")) {\n const categoryName = trimmedLine\n .substring(1, trimmedLine.length - 1)\n .trim();\n\n if (categoryNames.has(categoryName)) {\n throw new Error(`Duplicate category found: ${categoryName}`);\n }\n categoryNames.add(categoryName);\n\n currentCategory = { name: categoryName, ingredients: [] };\n this.categories.push(currentCategory);\n } else {\n if (currentCategory === null) {\n throw new Error(\n `Ingredient found without a category: ${trimmedLine}`,\n );\n }\n\n const aliases = trimmedLine.split(\"|\").map((s) => s.trim());\n for (const alias of aliases) {\n if (ingredientNames.has(alias)) {\n throw new Error(`Duplicate ingredient/alias found: ${alias}`);\n }\n ingredientNames.add(alias);\n }\n\n const ingredient: CategoryIngredient = {\n name: aliases[0]!, // We know this exists because trimmedLine is not empty\n aliases: aliases,\n };\n currentCategory.ingredients.push(ingredient);\n }\n }\n }\n}\n","import TOML from \"smol-toml\";\nimport type {\n FixedNumericValue,\n ProductOption,\n ProductOptionToml,\n ProductSize,\n} from \"../types\";\nimport type { TomlTable } from \"smol-toml\";\nimport {\n isPositiveIntegerString,\n parseQuantityInput,\n stringifyQuantityValue,\n} from \"../utils/parser_helpers\";\nimport { InvalidProductCatalogFormat } from \"../errors\";\n\n/**\n * Product Catalog Manager: used in conjunction with {@link ShoppingCart}\n *\n * ## Usage\n *\n * You can either directly populate the products by feeding the {@link ProductCatalog.products | products} property. Alternatively,\n * you can provide a catalog in TOML format to either the constructor itself or to the {@link ProductCatalog.parse | parse()} method.\n *\n * @category Classes\n *\n * @example\n * ```typescript\n * import { ProductCatalog } from \"@tmlmt/cooklang-parser\";\n *\n * const catalog = `\n * [eggs]\n * aliases = [\"oeuf\", \"huevo\"]\n * 01123 = { name = \"Single Egg\", size = \"1\", price = 2 }\n * 11244 = { name = \"Pack of 6 eggs\", size = \"6\", price = 10 }\n *\n * [flour]\n * aliases = [\"farine\", \"Mehl\"]\n * 01124 = { name = \"Small pack\", size = \"100%g\", price = 1.5 }\n * 14141 = { name = \"Big pack\", size = \"6%kg\", price = 10 }\n * `\n * const catalog = new ProductCatalog(catalog);\n * const eggs = catalog.find(\"oeuf\");\n * ```\n */\nexport class ProductCatalog {\n public products: ProductOption[] = [];\n\n constructor(tomlContent?: string) {\n if (tomlContent) this.parse(tomlContent);\n }\n\n /**\n * Parses a TOML string into a list of product options.\n * @param tomlContent - The TOML string to parse.\n * @returns A parsed list of `ProductOption`.\n */\n public parse(tomlContent: string): ProductOption[] {\n const catalogRaw = TOML.parse(tomlContent);\n\n // Reset internal state\n this.products = [];\n\n if (!this.isValidTomlContent(catalogRaw)) {\n throw new InvalidProductCatalogFormat();\n }\n\n for (const [ingredientName, ingredientData] of Object.entries(catalogRaw)) {\n const ingredientTable = ingredientData as TomlTable;\n const aliases = ingredientTable.aliases as string[] | undefined;\n\n for (const [key, productData] of Object.entries(ingredientTable)) {\n if (key === \"aliases\") {\n continue;\n }\n\n const productId = key;\n const { name, size, price, ...rest } =\n productData as unknown as ProductOptionToml;\n\n // Handle size as string or string[]\n const sizeStrings = Array.isArray(size) ? size : [size];\n const sizes: ProductSize[] = sizeStrings.map((sizeStr) => {\n const sizeAndUnitRaw = sizeStr.split(\"%\");\n const sizeParsed = parseQuantityInput(\n sizeAndUnitRaw[0]!,\n ) as FixedNumericValue;\n const productSize: ProductSize = { size: sizeParsed };\n if (sizeAndUnitRaw.length > 1) {\n productSize.unit = sizeAndUnitRaw[1]!;\n }\n return productSize;\n });\n\n const productOption: ProductOption = {\n id: productId,\n productName: name,\n ingredientName: ingredientName,\n price: price,\n sizes,\n ...rest,\n };\n if (aliases) {\n productOption.ingredientAliases = aliases;\n }\n\n this.products.push(productOption);\n }\n }\n\n return this.products;\n }\n\n /**\n * Stringifies the catalog to a TOML string.\n * @returns The TOML string representation of the catalog.\n */\n public stringify(): string {\n const grouped: Record<string, TomlTable> = {};\n\n for (const product of this.products) {\n const {\n id,\n ingredientName,\n ingredientAliases,\n sizes,\n productName,\n ...rest\n } = product;\n if (!grouped[ingredientName]) {\n grouped[ingredientName] = {};\n }\n if (ingredientAliases && !grouped[ingredientName].aliases) {\n grouped[ingredientName].aliases = ingredientAliases;\n }\n\n // Stringify each size as \"value%unit\" or just \"value\"\n const sizeStrings = sizes.map((s) =>\n s.unit\n ? `${stringifyQuantityValue(s.size)}%${s.unit}`\n : stringifyQuantityValue(s.size),\n );\n\n grouped[ingredientName][id] = {\n ...rest,\n name: productName,\n // Use array if multiple sizes, otherwise single string\n size: sizeStrings.length === 1 ? sizeStrings[0]! : sizeStrings,\n };\n }\n\n return TOML.stringify(grouped);\n }\n\n /**\n * Adds a product to the catalog.\n * @param productOption - The product to add.\n */\n public add(productOption: ProductOption): void {\n this.products.push(productOption);\n }\n\n /**\n * Removes a product from the catalog by its ID.\n * @param productId - The ID of the product to remove.\n */\n public remove(productId: string): void {\n this.products = this.products.filter((product) => product.id !== productId);\n }\n\n private isValidTomlContent(catalog: TomlTable): boolean {\n for (const productsRaw of Object.values(catalog)) {\n if (typeof productsRaw !== \"object\" || productsRaw === null) {\n return false;\n }\n\n for (const [id, obj] of Object.entries(productsRaw)) {\n if (id === \"aliases\") {\n if (!Array.isArray(obj)) {\n return false;\n }\n } else {\n if (!isPositiveIntegerString(id)) {\n return false;\n }\n if (typeof obj !== \"object\" || obj === null) {\n return false;\n }\n\n const record = obj as Record<string, unknown>;\n const keys = Object.keys(record);\n\n const mandatoryKeys = [\"name\", \"size\", \"price\"];\n\n if (mandatoryKeys.some((key) => !keys.includes(key))) {\n return false;\n }\n\n const hasProductName = typeof record.name === \"string\";\n // Size can be a string or an array of strings\n const hasSize =\n typeof record.size === \"string\" ||\n (Array.isArray(record.size) &&\n record.size.every((s) => typeof s === \"string\"));\n const hasPrice = typeof record.price === \"number\";\n\n if (!(hasProductName && hasSize && hasPrice)) {\n return false;\n }\n }\n }\n }\n\n return true;\n }\n}\n","type PartialBut<T, K extends keyof T> = Partial<T> & Pick<T, K>;\n\nconst escapeCache = new Map<string, string>();\n\nconst Flags = {\n GLOBAL: \"g\",\n NON_SENSITIVE: \"i\",\n MULTILINE: \"m\",\n DOT_ALL: \"s\",\n UNICODE: \"u\",\n STICKY: \"y\",\n} as const;\n\nconst Ranges = Object.freeze({\n digit: \"0-9\",\n lowercaseLetter: \"a-z\",\n uppercaseLetter: \"A-Z\",\n letter: \"a-zA-Z\",\n alphanumeric: \"a-zA-Z0-9\",\n anyCharacter: \".\",\n});\n\ntype RangeKeys = keyof typeof Ranges;\n\nconst Quantifiers = Object.freeze({\n zeroOrMore: \"*\",\n oneOrMore: \"+\",\n optional: \"?\",\n});\n\ntype Quantifiers =\n | \"exactly\"\n | \"atLeast\"\n | \"atMost\"\n | \"between\"\n | \"oneOrMore\"\n | \"zeroOrMore\"\n | \"repeat\";\ntype QuantifierMethods = Quantifiers | \"optional\" | \"lazy\";\n\ntype WithLazy = HumanRegex;\ntype Base = Omit<HumanRegex, \"lazy\">;\ntype AtStart = Omit<Base, QuantifierMethods | \"endGroup\">;\ntype AfterAnchor = Omit<Base, QuantifierMethods | \"or\">;\ntype SimpleQuantifier = Omit<Base, Quantifiers>;\ntype LazyQuantifier = Omit<WithLazy, Quantifiers>;\n\nclass HumanRegex {\n private parts: string[];\n private flags: Set<string>;\n\n constructor() {\n this.parts = [];\n this.flags = new Set<string>();\n }\n\n digit(): Base {\n return this.add(\"\\\\d\");\n }\n\n special(): Base {\n return this.add(\"(?=.*[!@#$%^&*])\");\n }\n\n word(): Base {\n return this.add(\"\\\\w\");\n }\n\n whitespace(): Base {\n return this.add(\"\\\\s\");\n }\n\n nonWhitespace(): Base {\n return this.add(\"\\\\S\");\n }\n\n literal(text: string): this {\n return this.add(escapeLiteral(text));\n }\n\n or(): AfterAnchor {\n return this.add(\"|\");\n }\n\n range(name: RangeKeys): Base {\n const range = Ranges[name];\n if (!range) throw new Error(`Unknown range: ${name}`);\n return this.add(`[${range}]`);\n }\n\n notRange(name: RangeKeys): Base {\n const range = Ranges[name];\n if (!range) throw new Error(`Unknown range: ${name}`);\n return this.add(`[^${range}]`);\n }\n\n anyOf(chars: string): Base {\n return this.add(`[${chars}]`);\n }\n\n notAnyOf(chars: string): Base {\n return this.add(`[^${chars}]`);\n }\n\n lazy(): Base {\n const lastPart = this.parts.pop();\n if (!lastPart) throw new Error(\"No quantifier to make lazy\");\n return this.add(`${lastPart}?`);\n }\n\n letter(): Base {\n return this.add(\"[a-zA-Z]\");\n }\n\n anyCharacter(): Base {\n return this.add(\".\");\n }\n\n newline(): Base {\n return this.add(\"(\\\\r\\\\n|\\\\r|\\\\n)\"); // Windows: \\r\\n, Unix: \\n, Old Macs: \\r\n }\n\n negativeLookahead(pattern: string): Base {\n return this.add(`(?!${pattern})`);\n }\n\n positiveLookahead(pattern: string): Base {\n return this.add(`(?=${pattern})`);\n }\n\n positiveLookbehind(pattern: string): Base {\n return this.add(`(?<=${pattern})`);\n }\n\n negativeLookbehind(pattern: string): Base {\n return this.add(`(?<!${pattern})`);\n }\n\n hasSpecialCharacter(): Base {\n return this.add(\"(?=.*[!@#$%^&*])\");\n }\n\n hasDigit(): Base {\n return this.add(\"(?=.*\\\\d)\");\n }\n\n hasLetter(): Base {\n return this.add(\"(?=.*[a-zA-Z])\");\n }\n\n optional(): SimpleQuantifier {\n return this.add(Quantifiers.optional);\n }\n\n exactly(n: number): SimpleQuantifier {\n return this.add(`{${n}}`);\n }\n\n atLeast(n: number): LazyQuantifier {\n return this.add(`{${n},}`);\n }\n\n atMost(n: number): LazyQuantifier {\n return this.add(`{0,${n}}`);\n }\n\n between(min: number, max: number): LazyQuantifier {\n return this.add(`{${min},${max}}`);\n }\n\n oneOrMore(): LazyQuantifier {\n return this.add(Quantifiers.oneOrMore);\n }\n\n zeroOrMore(): LazyQuantifier {\n return this.add(Quantifiers.zeroOrMore);\n }\n\n startNamedGroup(name: string): AfterAnchor {\n return this.add(`(?<${name}>`);\n }\n\n startGroup(): AfterAnchor {\n return this.add(\"(?:\");\n }\n\n startCaptureGroup(): AfterAnchor {\n return this.add(\"(\");\n }\n\n wordBoundary(): Base {\n return this.add(\"\\\\b\");\n }\n\n nonWordBoundary(): Base {\n return this.add(\"\\\\B\");\n }\n\n endGroup(): Base {\n return this.add(\")\");\n }\n\n startAnchor(): AfterAnchor {\n return this.add(\"^\");\n }\n\n endAnchor(): AfterAnchor {\n return this.add(\"$\");\n }\n\n global(): this {\n this.flags.add(Flags.GLOBAL);\n return this;\n }\n\n nonSensitive(): this {\n this.flags.add(Flags.NON_SENSITIVE);\n return this;\n }\n\n multiline(): this {\n this.flags.add(Flags.MULTILINE);\n return this;\n }\n\n dotAll(): this {\n this.flags.add(Flags.DOT_ALL);\n return this;\n }\n\n sticky(): this {\n this.flags.add(Flags.STICKY);\n return this;\n }\n\n unicodeChar(variant?: \"u\" | \"l\" | \"t\" | \"m\" | \"o\"): Base {\n this.flags.add(Flags.UNICODE);\n const validVariants = new Set([\"u\", \"l\", \"t\", \"m\", \"o\"] as const);\n\n if (variant !== undefined && !validVariants.has(variant)) {\n throw new Error(`Invalid Unicode letter variant: ${variant}`);\n }\n\n return this.add(`\\\\p{L${variant ?? \"\"}}`);\n }\n\n unicodeDigit(): Base {\n this.flags.add(Flags.UNICODE);\n return this.add(\"\\\\p{N}\");\n }\n\n unicodePunctuation(): Base {\n this.flags.add(Flags.UNICODE);\n return this.add(\"\\\\p{P}\");\n }\n\n unicodeSymbol(): Base {\n this.flags.add(Flags.UNICODE);\n return this.add(\"\\\\p{S}\");\n }\n\n repeat(count: number): Base {\n if (this.parts.length === 0) {\n throw new Error(\"No pattern to repeat\");\n }\n\n const lastPart = this.parts.pop();\n this.parts.push(`(${lastPart}){${count}}`);\n return this;\n }\n\n ipv4Octet(): Base {\n return this.add(\"(25[0-5]|2[0-4]\\\\d|1\\\\d\\\\d|[1-9]\\\\d|\\\\d)\");\n }\n\n protocol(): Base {\n return this.add(\"https?://\");\n }\n\n www(): Base {\n return this.add(\"(www\\\\.)?\");\n }\n\n tld(): Base {\n return this.add(\"(com|org|net)\");\n }\n\n path(): Base {\n return this.add(\"(/\\\\w+)*\");\n }\n\n private add(part: string): this {\n this.parts.push(part);\n return this;\n }\n\n toString(): string {\n return this.parts.join(\"\");\n }\n\n toRegExp(): RegExp {\n return new RegExp(this.toString(), [...this.flags].join(\"\"));\n }\n}\n\nfunction escapeLiteral(text: string): string {\n if (!escapeCache.has(text)) {\n escapeCache.set(text, text.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\"));\n }\n return escapeCache.get(text)!;\n}\n\nconst createRegex = (): AtStart => new HumanRegex();\n\nconst Patterns = (() => {\n const createCachedPattern = (\n builder: () => PartialBut<HumanRegex, \"toRegExp\">\n ) => {\n const regex = builder().toRegExp();\n return () => new RegExp(regex.source, regex.flags);\n };\n\n return {\n email: createCachedPattern(() =>\n createRegex()\n .startAnchor()\n .word()\n .oneOrMore()\n .literal(\"@\")\n .word()\n .oneOrMore()\n .startGroup()\n .literal(\".\")\n .word()\n .oneOrMore()\n .endGroup()\n .zeroOrMore()\n .literal(\".\")\n .letter()\n .atLeast(2)\n .endAnchor()\n ),\n url: createCachedPattern(() =>\n createRegex()\n .startAnchor()\n .protocol()\n .www()\n .word()\n .oneOrMore()\n .literal(\".\")\n .tld()\n .path()\n .endAnchor()\n ),\n phoneInternational: createCachedPattern(() =>\n createRegex()\n .startAnchor()\n .literal(\"+\")\n .digit()\n .between(1, 3)\n .literal(\"-\")\n .digit()\n .between(3, 14)\n .endAnchor()\n ),\n };\n})();\n\nexport { createRegex, Patterns, Flags, Ranges, Quantifiers };\n","import { createRegex } from \"human-regex\";\n\n/** Matches all top-level metadata keys in frontmatter content */\nexport const metadataKeyRegex = /^([^:\\n]+?):/gm;\n\n/** Matches a number (integer or decimal, optionally negative) */\nexport const numericValueRegex = /^-?\\d+(\\.\\d+)?$/;\n\n/** Creates a regex to match a nested YAML-style object for a given key \n * \n * Nested properties should be indented at least by one space and can be nested arbitrarily deep. Tabs are not allowed for indentation.\n * Lines can be key-value pairs or list items (starting with `-`).\n*/\nexport const nestedMetaVarRegex = (varName: string): RegExp =>\n new RegExp(\n `^${varName}:\\\\s*\\\\r?\\\\n((?:[ ]+.+(?:\\\\r?\\\\n|$))+)`,\n \"m\",\n );\n\nexport const metadataRegex = createRegex()\n .literal(\"---\").newline()\n .startCaptureGroup().anyCharacter().zeroOrMore().optional().endGroup()\n .newline().literal(\"---\")\n .dotAll().toRegExp();\n\nexport const scalingMetaValueRegex = (varName: string): RegExp => createRegex()\n .startAnchor()\n .literal(varName)\n .literal(\":\")\n .anyOf(\"\\\\t \").zeroOrMore()\n .startCaptureGroup()\n .startCaptureGroup()\n .notAnyOf(\",\\\\n\").oneOrMore()\n .endGroup()\n .startGroup()\n .literal(\",\")\n .whitespace().zeroOrMore()\n .startCaptureGroup()\n .anyCharacter().oneOrMore()\n .endGroup()\n .endGroup().optional()\n .endGroup()\n .endAnchor()\n .multiline()\n .toRegExp()\n\nconst nonWordChar = \"\\\\s@#~\\\\[\\\\]{(,;:!?\"\nconst nonWordCharStrict = \"\\\\s@#~\\\\[\\\\]{(,;:!?|\"\n\nexport const ingredientWithAlternativeRegex = createRegex()\n .literal(\"@\")\n .startNamedGroup(\"ingredientModifiers\")\n .anyOf(\"@\\\\-&?\").zeroOrMore()\n .endGroup().optional()\n .startNamedGroup(\"ingredientRecipeAnchor\")\n .literal(\"./\")\n .endGroup().optional()\n .startGroup()\n .startGroup()\n .startNamedGroup(\"mIngredientName\")\n .notAnyOf(nonWordChar).oneOrMore()\n .startGroup()\n .whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore()\n .endGroup().oneOrMore()\n .endGroup()\n .positiveLookahead(\"\\\\s*(?:\\\\{[^\\\\}]*\\\\}|\\\\([^)]*\\\\))\")\n .endGroup()\n .or()\n .startNamedGroup(\"sIngredientName\")\n .notAnyOf(nonWordChar).zeroOrMore()\n .notAnyOf(\"\\\\.\"+nonWordChar)\n .endGroup()\n .endGroup()\n .startGroup()\n .literal(\"{\")\n .startNamedGroup(\"ingredientQuantityModifier\")\n .literal(\"=\").exactly(1)\n .endGroup().optional()\n .startNamedGroup(\"ingredientQuantity\")\n .startGroup()\n .notAnyOf(\"}|%\").oneOrMore()\n .endGroup().optional()\n .startGroup()\n .literal(\"%\")\n .notAnyOf(\"|}\").oneOrMore().lazy()\n .endGroup().optional()\n .startGroup()\n .literal(\"|\")\n .notAnyOf(\"}\").oneOrMore().lazy()\n .endGroup().zeroOrMore()\n .endGroup()\n .literal(\"}\")\n .endGroup().optional()\n .startGroup()\n .literal(\"(\")\n .startNamedGroup(\"ingredientPreparation\")\n .notAnyOf(\")\").oneOrMore().lazy()\n .endGroup()\n .literal(\")\")\n .endGroup().optional()\n .startGroup()\n .literal(\"[\")\n .startNamedGroup(\"ingredientNote\")\n .notAnyOf(\"\\\\]\").oneOrMore().lazy()\n .endGroup()\n .literal(\"]\")\n .endGroup().optional()\n .startNamedGroup(\"ingredientAlternative\")\n .startGroup()\n .literal(\"|\")\n .startGroup()\n .anyOf(\"@\\\\-&?\").zeroOrMore()\n .endGroup().optional()\n .startGroup()\n .literal(\"./\")\n .endGroup().optional()\n .startGroup()\n .startGroup()\n .startGroup()\n .notAnyOf(nonWordChar).oneOrMore()\n .startGroup()\n .whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore()\n .endGroup().oneOrMore()\n .endGroup()\n .positiveLookahead(\"\\\\s*(?:\\\\{[^\\\\}]*\\\\}|\\\\([^)]*\\\\))\")\n .endGroup()\n .or()\n .startGroup()\n .notAnyOf(nonWordChar).oneOrMore()\n .endGroup()\n .endGroup()\n .startGroup()\n .literal(\"{\")\n .startGroup()\n .literal(\"=\").exactly(1)\n .endGroup().optional()\n .startGroup()\n .notAnyOf(\"}%\").oneOrMore()\n .endGroup().optional()\n .startGroup()\n .literal(\"%\")\n .startGroup()\n .notAnyOf(\"}\").oneOrMore().lazy()\n .endGroup()\n .endGroup().optional()\n .literal(\"}\")\n .endGroup().optional()\n .startGroup()\n .literal(\"(\")\n .startGroup()\n .notAnyOf(\")\").oneOrMore().lazy()\n .endGroup()\n .literal(\")\")\n .endGroup().optional()\n .startGroup()\n .literal(\"[\")\n .startGroup()\n .notAnyOf(\"\\\\]\").oneOrMore().lazy()\n .endGroup()\n .literal(\"]\")\n .endGroup().optional()\n .endGroup().zeroOrMore()\n .endGroup()\n .toRegExp();\n\nexport const inlineIngredientAlternativesRegex = new RegExp(\"\\\\|\" + ingredientWithAlternativeRegex.source.slice(1))\n\nexport const quantityAlternativeRegex = createRegex()\n .startNamedGroup(\"quantity\")\n .notAnyOf(\"}|%\").oneOrMore()\n .endGroup().optional()\n .startGroup()\n .literal(\"%\")\n .startNamedGroup(\"unit\")\n .notAnyOf(\"|}\").oneOrMore()\n .endGroup()\n .endGroup().optional()\n .startGroup()\n .literal(\"|\")\n .startNamedGroup(\"alternative\")\n .startGroup()\n .notAnyOf(\"}\").oneOrMore()\n .endGroup().zeroOrMore()\n .endGroup()\n .endGroup().optional()\n .toRegExp()\n \nexport const ingredientWithGroupKeyRegex = createRegex()\n .literal(\"@|\")\n .startNamedGroup(\"gIngredientGroupKey\")\n .notAnyOf(nonWordCharStrict).oneOrMore()\n .endGroup()\n .literal(\"|\")\n .startNamedGroup(\"gIngredientModifiers\")\n .anyOf(\"@\\\\-&?\").zeroOrMore()\n .endGroup().optional()\n .startNamedGroup(\"gIngredientRecipeAnchor\")\n .literal(\"./\")\n .endGroup().optional()\n .startGroup()\n .startGroup()\n .startNamedGroup(\"gmIngredientName\")\n .notAnyOf(nonWordChar).oneOrMore()\n .startGroup()\n .whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore()\n .endGroup().oneOrMore()\n .endGroup()\n .positiveLookahead(\"\\\\s*(?:\\\\{[^\\\\}]*\\\\}|\\\\([^)]*\\\\))\")\n .endGroup()\n .or()\n .startNamedGroup(\"gsIngredientName\")\n .notAnyOf(nonWordChar).zeroOrMore()\n .notAnyOf(\"\\\\.\"+nonWordChar)\n .endGroup()\n .endGroup()\n .startGroup()\n .literal(\"{\")\n .startNamedGroup(\"gIngredientQuantityModifier\")\n .literal(\"=\").exactly(1)\n .endGroup().optional()\n .startNamedGroup(\"gIngredientQuantity\")\n .startGroup()\n .notAnyOf(\"}|%\").oneOrMore()\n .endGroup().optional()\n .startGroup()\n .literal(\"%\")\n .notAnyOf(\"|}\").oneOrMore().lazy()\n .endGroup().optional()\n .startGroup()\n .literal(\"|\")\n .notAnyOf(\"}\").oneOrMore().lazy()\n .endGroup().zeroOrMore()\n .endGroup()\n .literal(\"}\")\n .endGroup().optional()\n .startGroup()\n .literal(\"(\")\n .startNamedGroup(\"gIngredientPreparation\")\n .notAnyOf(\")\").oneOrMore().lazy()\n .endGroup()\n .literal(\")\")\n .endGroup().optional()\n .toRegExp()\n\nexport const ingredientAliasRegex = createRegex()\n .startAnchor()\n .startNamedGroup(\"ingredientListName\")\n .notAnyOf(\"|\").oneOrMore()\n .endGroup()\n .literal(\"|\")\n .startNamedGroup(\"ingredientDisplayName\")\n .notAnyOf(\"|\").oneOrMore()\n .endGroup()\n .endAnchor()\n .toRegExp();\n\nexport const cookwareRegex = createRegex()\n .literal(\"#\")\n .startNamedGroup(\"cookwareModifiers\")\n .anyOf(\"\\\\-&?\").zeroOrMore()\n .endGroup()\n .startGroup()\n .startGroup()\n .startNamedGroup(\"mCookwareName\")\n .notAnyOf(nonWordChar).oneOrMore()\n .startGroup()\n .whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore()\n .endGroup().oneOrMore()\n .endGroup().positiveLookahead(\"\\\\s*(?:\\\\{[^\\\\}]*\\\\})\")\n .endGroup()\n .or()\n .startNamedGroup(\"sCookwareName\")\n .notAnyOf(nonWordChar).zeroOrMore()\n .notAnyOf(\"\\\\.\"+nonWordChar)\n .endGroup()\n .endGroup()\n .startGroup()\n .literal(\"{\")\n .startNamedGroup(\"cookwareQuantity\")\n .anyCharacter().zeroOrMore().lazy()\n .endGroup()\n .literal(\"}\")\n .endGroup().optional()\n .toRegExp();\n\nconst timerRegex = createRegex()\n .literal(\"~\")\n .startNamedGroup(\"timerName\")\n .anyCharacter().zeroOrMore().lazy()\n .endGroup()\n .literal(\"{\")\n .startNamedGroup(\"timerQuantity\")\n .anyCharacter().oneOrMore().lazy()\n .endGroup()\n .startGroup()\n .literal(\"%\")\n .startNamedGroup(\"timerUnit\")\n .anyCharacter().oneOrMore().lazy()\n .endGroup()\n .endGroup().optional()\n .literal(\"}\")\n .toRegExp()\n\nexport const arbitraryScalableRegex = createRegex()\n .literal(\"{{\")\n .startGroup()\n .startNamedGroup(\"arbitraryName\")\n .notAnyOf(\"}:%\").oneOrMore()\n .endGroup()\n .literal(\":\")\n .endGroup().optional()\n .startNamedGroup(\"arbitraryQuantity\")\n .startGroup()\n .notAnyOf(\"}|%\").oneOrMore()\n .endGroup().optional()\n .startGroup()\n .literal(\"%\")\n .notAnyOf(\"|}\").oneOrMore().lazy()\n .endGroup().optional()\n .startGroup()\n .literal(\"|\")\n .notAnyOf(\"}\").oneOrMore().lazy()\n .endGroup().zeroOrMore()\n .endGroup()\n .literal(\"}}\")\n .toRegExp();\n\nexport const tokensRegex = new RegExp(\n [\n ingredientWithGroupKeyRegex,\n ingredientWithAlternativeRegex,\n cookwareRegex,\n timerRegex,\n arbitraryScalableRegex\n ]\n .map((r) => r.source)\n .join(\"|\"),\n \"gu\",\n);\n\nexport const commentRegex = createRegex()\n .literal(\"--\")\n .anyCharacter().zeroOrMore()\n .global()\n .toRegExp();\n\nexport const blockCommentRegex = createRegex()\n .literal(\"[-\")\n .anyCharacter().zeroOrMore().lazy()\n .literal(\"-]\")\n .whitespace().zeroOrMore()\n .global()\n .toRegExp();\n\nexport const shoppingListRegex = createRegex()\n .literal(\"[\")\n .startNamedGroup(\"name\")\n .anyCharacter().oneOrMore()\n .endGroup()\n .literal(\"]\")\n .newline()\n .startNamedGroup(\"items\")\n .anyCharacter().zeroOrMore().lazy()\n .endGroup()\n .startGroup()\n .newline().newline()\n .or()\n .endAnchor()\n .endGroup()\n .global()\n .toRegExp()\n\nexport const rangeRegex = createRegex()\n .startAnchor()\n .digit().oneOrMore()\n .startGroup()\n .anyOf(\".,/\").exactly(1)\n .digit().oneOrMore()\n .endGroup().optional()\n .literal(\"-\")\n .digit().oneOrMore()\n .startGroup()\n .anyOf(\".,/\").exactly(1)\n .digit().oneOrMore()\n .endGroup().optional()\n .endAnchor()\n .toRegExp()\n\nexport const numberLikeRegex = createRegex()\n .startAnchor()\n .digit().oneOrMore()\n .startGroup()\n .anyOf(\".,/\").exactly(1)\n .digit().oneOrMore()\n .endGroup().optional()\n .endAnchor()\n .toRegExp()\n\nexport const floatRegex = createRegex()\n .startAnchor()\n .digit().oneOrMore()\n .startGroup()\n .anyOf(\".\").exactly(1)\n .digit().oneOrMore()\n .endGroup().optional()\n .endAnchor()\n .toRegExp()","import type { UnitDefinition, UnitDefinitionLike } from \"../types\";\n\n// Base units: mass -> gram (g), volume -> milliliter (ml)\nexport const units: UnitDefinition[] = [\n // Mass (Metric)\n {\n name: \"g\",\n type: \"mass\",\n system: \"metric\",\n aliases: [\"gram\", \"grams\", \"grammes\"],\n toBase: 1,\n maxValue: 999,\n },\n {\n name: \"kg\",\n type: \"mass\",\n system: \"metric\",\n aliases: [\"kilogram\", \"kilograms\", \"kilogrammes\", \"kilos\", \"kilo\"],\n toBase: 1000,\n },\n // Mass (US/UK - identical in both systems)\n {\n name: \"oz\",\n type: \"mass\",\n system: \"ambiguous\",\n aliases: [\"ounce\", \"ounces\"],\n toBase: 28.3495, // default: US (same as UK)\n toBaseBySystem: { US: 28.3495, UK: 28.3495 },\n maxValue: 31, // 16 oz = 1 lb, allow a bit more\n fractions: { enabled: true, denominators: [2] },\n },\n {\n name: \"lb\",\n type: \"mass\",\n system: \"ambiguous\",\n aliases: [\"pound\", \"pounds\"],\n toBase: 453.592, // default: US (same as UK)\n toBaseBySystem: { US: 453.592, UK: 453.592 },\n fractions: { enabled: true, denominators: [2, 4] },\n },\n\n // Volume (Metric)\n {\n name: \"ml\",\n type: \"volume\",\n system: \"metric\",\n aliases: [\"milliliter\", \"milliliters\", \"millilitre\", \"millilitres\", \"cc\"],\n toBase: 1,\n maxValue: 999,\n },\n {\n name: \"cl\",\n type: \"volume\",\n system: \"metric\",\n aliases: [\"centiliter\", \"centiliters\", \"centilitre\", \"centilitres\"],\n toBase: 10,\n isBestUnit: false, // exists but not a \"best\" candidate\n },\n {\n name: \"dl\",\n type: \"volume\",\n system: \"metric\",\n aliases: [\"deciliter\", \"deciliters\", \"decilitre\", \"decilitres\"],\n toBase: 100,\n isBestUnit: false, // exists but not a \"best\" candidate\n },\n {\n name: \"l\",\n type: \"volume\",\n system: \"metric\",\n aliases: [\"liter\", \"liters\", \"litre\", \"litres\"],\n toBase: 1000,\n },\n\n // Volume (JP)\n {\n name: \"go\",\n type: \"volume\",\n system: \"JP\",\n aliases: [\"gou\", \"goo\", \"合\", \"rice cup\"],\n toBase: 180,\n maxValue: 10,\n },\n\n // Volume (Ambiguous: metric/US/UK)\n {\n name: \"tsp\",\n type: \"volume\",\n system: \"ambiguous\",\n aliases: [\"teaspoon\", \"teaspoons\"],\n toBase: 5, // default: metric\n toBaseBySystem: { metric: 5, US: 4.929, UK: 5.919, JP: 5 },\n maxValue: 5, // 3 tsp = 1 tbsp (but allow a bit more)\n fractions: { enabled: true, denominators: [2, 3, 4, 8] },\n },\n {\n name: \"tbsp\",\n type: \"volume\",\n system: \"ambiguous\",\n aliases: [\"tablespoon\", \"tablespoons\"],\n toBase: 15, // default: metric\n toBaseBySystem: { metric: 15, US: 14.787, UK: 17.758, JP: 15 },\n maxValue: 4, // ~16 tbsp = 1 cup\n fractions: { enabled: true },\n },\n\n // Volume (Ambiguous: US/UK only)\n {\n name: \"fl-oz\",\n type: \"volume\",\n system: \"ambiguous\",\n aliases: [\"fluid ounce\", \"fluid ounces\"],\n toBase: 29.5735, // default: US\n toBaseBySystem: { US: 29.5735, UK: 28.4131 },\n maxValue: 15, // 8 fl-oz ~ 1 cup, allow more\n fractions: { enabled: true, denominators: [2] },\n },\n {\n name: \"cup\",\n type: \"volume\",\n system: \"ambiguous\",\n aliases: [\"cups\"],\n toBase: 236.588, // default: US\n toBaseBySystem: { US: 236.588, UK: 284.131 },\n maxValue: 15, // upgrade to gallons above 15 cups\n fractions: { enabled: true },\n },\n {\n name: \"pint\",\n type: \"volume\",\n system: \"ambiguous\",\n aliases: [\"pints\"],\n toBase: 473.176, // default: US\n toBaseBySystem: { US: 473.176, UK: 568.261 },\n maxValue: 3, // 2 pints = 1 quart\n fractions: { enabled: true, denominators: [2] },\n isBestUnit: false, // exists but not a \"best\" candidate\n },\n {\n name: \"quart\",\n type: \"volume\",\n system: \"ambiguous\",\n aliases: [\"quarts\"],\n toBase: 946.353, // default: US\n toBaseBySystem: { US: 946.353, UK: 1136.52 },\n maxValue: 3, // 4 quarts = 1 gallon\n fractions: { enabled: true, denominators: [2] },\n isBestUnit: false, // exists but not a \"best\" candidate\n },\n {\n name: \"gallon\",\n type: \"volume\",\n system: \"ambiguous\",\n aliases: [\"gallons\"],\n toBase: 3785.41, // default: US\n toBaseBySystem: { US: 3785.41, UK: 4546.09 },\n fractions: { enabled: true, denominators: [2] },\n },\n\n // Count units (no conversion, but recognized as a type)\n {\n name: \"piece\",\n type: \"count\",\n system: \"metric\",\n aliases: [\"pieces\", \"pc\"],\n toBase: 1,\n maxValue: 999,\n },\n];\n\nconst unitMap = new Map<string, UnitDefinition>();\nfor (const unit of units) {\n unitMap.set(unit.name.toLowerCase(), unit);\n for (const alias of unit.aliases) {\n unitMap.set(alias.toLowerCase(), unit);\n }\n}\n\nexport function normalizeUnit(unit: string = \"\"): UnitDefinition | undefined {\n return unitMap.get(unit.toLowerCase().trim());\n}\n\nexport const NO_UNIT = \"__no-unit__\";\n\nexport function resolveUnit(\n name: string = NO_UNIT,\n integerProtected: boolean = false,\n): UnitDefinitionLike {\n const normalizedUnit = normalizeUnit(name);\n const resolvedUnit: UnitDefinitionLike = normalizedUnit\n ? { ...normalizedUnit, name }\n : { name, type: \"other\", system: \"none\" };\n return integerProtected\n ? { ...resolvedUnit, integerProtected: true }\n : resolvedUnit;\n}\n\nexport function isNoUnit(unit?: UnitDefinitionLike): boolean {\n if (!unit) return true;\n return resolveUnit(unit.name).name === NO_UNIT;\n}\n","import Big from \"big.js\";\nimport type { QuantityWithUnitDef } from \"../types\";\nimport {\n getAverageValue,\n approximateFraction,\n DEFAULT_DENOMINATORS,\n DEFAULT_FRACTION_ACCURACY,\n DEFAULT_MAX_WHOLE,\n} from \"../quantities/numeric\";\nimport { UnitDefinition, SpecificUnitSystem, UnitType } from \"../types\";\nimport { isUnitCompatibleWithSystem } from \"./compatibility\";\nimport { units } from \"./definitions\";\n\nconst EPSILON = 0.01;\nconst DEFAULT_MAX_VALUE = 999;\n\n/**\n * Check if a value is \"close enough\" to an integer (within epsilon).\n */\nfunction isCloseToInteger(value: number): boolean {\n return Math.abs(value - Math.round(value)) < EPSILON;\n}\n\n/**\n * Get the maximum value threshold for a unit.\n * Beyond this value, we should upgrade to a larger unit.\n */\nfunction getMaxValue(unit: UnitDefinition): number {\n return unit.maxValue ?? DEFAULT_MAX_VALUE;\n}\n\n/**\n * Check if a value is in the valid range for a unit.\n * A value is valid if:\n * - It's \\>= 1 AND \\<= maxValue, OR\n * - It's \\< 1 AND can be approximated as a fraction (for units with fractions enabled)\n */\nfunction isValueInRange(value: number, unit: UnitDefinition): boolean {\n const maxValue = getMaxValue(unit);\n\n // Standard range: 1 to maxValue\n if (value >= 1 && value <= maxValue) {\n return true;\n }\n\n // Fraction range: values < 1 that can be approximated as fractions\n if (value > 0 && value < 1 && unit.fractions?.enabled) {\n const denominators = unit.fractions.denominators ?? DEFAULT_DENOMINATORS;\n const maxWhole = unit.fractions.maxWhole ?? DEFAULT_MAX_WHOLE;\n const fraction = approximateFraction(\n value,\n denominators,\n DEFAULT_FRACTION_ACCURACY,\n maxWhole,\n );\n return fraction !== null;\n }\n\n return false;\n}\n\n/**\n * Find the best unit for displaying a quantity.\n *\n * Algorithm:\n * 1. Get all candidate units of the same type that are compatible with the system\n * 2. Filter to candidates where value \\>= 1 and value \\<= maxValue (per-unit threshold)\n * 3. Only consider units with isBestUnit !== false\n * 4. Score: prefer integers in input family → integers in any family → smallest in range\n * 5. If none in range, pick the one closest to the range\n *\n * @param valueInBase - The value in base units (e.g., grams for mass, mL for volume)\n * @param unitType - The type of unit (mass, volume, count)\n * @param system - The system to use for conversion (metric, US, UK, JP)\n * @param inputUnits - The original input units (used as preferred \"family\")\n * @returns The best unit definition and the converted value\n */\nexport function findBestUnit(\n valueInBase: number,\n unitType: UnitType,\n system: SpecificUnitSystem,\n inputUnits: UnitDefinition[],\n): { unit: UnitDefinition; value: number } {\n const inputUnitNames = new Set(inputUnits.map((u) => u.name));\n // Get all candidate units of the same type compatible with the system, including input units\n const candidates = units.filter(\n (u) =>\n u.type === unitType &&\n isUnitCompatibleWithSystem(u, system) &&\n (u.isBestUnit !== false || inputUnitNames.has(u.name)),\n );\n\n /* v8 ignore start -- @preserve: defensive fallback that shouldn't happen with valid inputs */\n if (candidates.length === 0) {\n // Fallback: shouldn't happen, but return first input unit\n const fallbackUnit = inputUnits[0]!;\n return {\n unit: fallbackUnit,\n value: valueInBase / getToBase(fallbackUnit, system),\n };\n }\n /* v8 ignore stop */\n\n // Calculate value for each candidate\n const candidatesWithValues = candidates.map((unit) => ({\n unit,\n value: valueInBase / getToBase(unit, system),\n }));\n\n // Filter to valid range (including fraction-representable values), only for best-unit candidates\n const inRange = candidatesWithValues.filter((c) =>\n isValueInRange(c.value, c.unit),\n );\n\n if (inRange.length > 0) {\n // First priority: integers in input family\n const integersInInputFamily = inRange.filter(\n (c) => isCloseToInteger(c.value) && inputUnitNames.has(c.unit.name),\n );\n if (integersInInputFamily.length > 0) {\n // Return smallest integer in input family\n return integersInInputFamily.sort((a, b) => a.value - b.value)[0]!;\n }\n\n // Second priority: integers in any family (prefer system-appropriate units)\n const integersAny = inRange.filter((c) => isCloseToInteger(c.value));\n if (integersAny.length > 0) {\n // Sort by value\n return integersAny.sort((a, b) => a.value - b.value)[0]!;\n }\n\n // Third priority: smallest value in range (prioritizing input family)\n return inRange.sort((a, b) => {\n // Prioritize input family\n const aInFamily = inputUnitNames.has(a.unit.name) ? 0 : 1;\n const bInFamily = inputUnitNames.has(b.unit.name) ? 0 : 1;\n if (aInFamily !== bInFamily) return aInFamily - bInFamily;\n // Then by smallest value\n return a.value - b.value;\n })[0]!;\n }\n\n return candidatesWithValues.sort((a, b) => {\n const aMaxValue = getMaxValue(a.unit);\n const bMaxValue = getMaxValue(b.unit);\n const aDistance = a.value < 1 ? 1 - a.value : a.value - aMaxValue;\n const bDistance = b.value < 1 ? 1 - b.value : b.value - bMaxValue;\n return aDistance - bDistance;\n })[0]!;\n}\n\nexport function getUnitRatio(q1: QuantityWithUnitDef, q2: QuantityWithUnitDef) {\n const q1Value = getAverageValue(q1.quantity);\n const q2Value = getAverageValue(q2.quantity);\n const factor =\n \"toBase\" in q1.unit && \"toBase\" in q2.unit\n ? q1.unit.toBase / q2.unit.toBase\n : 1;\n\n if (typeof q1Value !== \"number\" || typeof q2Value !== \"number\") {\n throw Error(\n \"One of both values is not a number, so a ratio cannot be computed\",\n );\n }\n return Big(q1Value).times(factor).div(q2Value);\n}\n\nexport function getBaseUnitRatio(\n q: QuantityWithUnitDef,\n qRef: QuantityWithUnitDef,\n) {\n if (\"toBase\" in q.unit && \"toBase\" in qRef.unit) {\n return q.unit.toBase / qRef.unit.toBase;\n } else {\n return 1;\n }\n}\n\n/**\n * Get the toBase conversion factor for a unit, considering the system context.\n *\n * For ambiguous units:\n * - If a specific system is provided and the unit supports it, use that system's factor\n * - Otherwise, fall back to the unit's default toBase\n *\n * @param unit - The unit definition\n * @param system - Optional system context to use for ambiguous units\n * @returns The appropriate toBase conversion factor\n */\nexport function getToBase(\n unit: UnitDefinition,\n system?: SpecificUnitSystem,\n): number {\n if (unit.system === \"ambiguous\" && system && unit.toBaseBySystem) {\n return unit.toBaseBySystem[system] ?? unit.toBase;\n }\n return unit.toBase;\n}\n","import Big from \"big.js\";\nimport type {\n DecimalValue,\n FractionValue,\n FixedValue,\n Range,\n UnitDefinition,\n} from \"../types\";\n\n/** Default allowed denominators for fraction approximation */\nexport const DEFAULT_DENOMINATORS = [2, 3, 4];\n/** Default accuracy tolerance for fraction approximation (5%) */\nexport const DEFAULT_FRACTION_ACCURACY = 0.05;\n/** Default maximum whole number in mixed fraction before falling back to decimal */\nexport const DEFAULT_MAX_WHOLE = 4;\n\nfunction gcd(a: number, b: number): number {\n return b === 0 ? a : gcd(b, a % b);\n}\n\nexport function simplifyFraction(\n num: number,\n den: number,\n): DecimalValue | FractionValue {\n if (den === 0) {\n throw new Error(\"Denominator cannot be zero.\");\n }\n\n const commonDivisor = gcd(Math.abs(num), Math.abs(den));\n let simplifiedNum = num / commonDivisor;\n let simplifiedDen = den / commonDivisor;\n if (simplifiedDen < 0) {\n simplifiedNum = -simplifiedNum;\n simplifiedDen = -simplifiedDen;\n }\n\n if (simplifiedDen === 1) {\n return { type: \"decimal\", decimal: simplifiedNum };\n } else {\n return { type: \"fraction\", num: simplifiedNum, den: simplifiedDen };\n }\n}\n\n/**\n * Approximates a decimal value as a fraction within a given accuracy tolerance.\n * Returns an improper fraction (e.g., 1.25 → \\{ num: 5, den: 4 \\}) or null if no good match.\n *\n * @param value - The decimal value to approximate\n * @param denominators - Allowed denominators (default: [2, 3, 4, 8])\n * @param accuracy - Maximum relative error tolerance (default: 0.05 = 5%)\n * @param maxWhole - Maximum whole number part before returning null (default: 4)\n * @returns FractionValue if a good approximation exists, null otherwise\n */\nexport function approximateFraction(\n value: number,\n denominators: number[] = DEFAULT_DENOMINATORS,\n accuracy: number = DEFAULT_FRACTION_ACCURACY,\n maxWhole: number = DEFAULT_MAX_WHOLE,\n): FractionValue | null {\n // Only handle positive values\n if (value <= 0 || !Number.isFinite(value)) {\n return null;\n }\n\n // Check if whole part exceeds maxWhole\n const wholePart = Math.floor(value);\n if (wholePart > maxWhole) {\n return null;\n }\n\n // If value is very close to an integer, return null (use decimal instead)\n const fractionalPart = value - wholePart;\n if (fractionalPart < 1e-4) {\n return null;\n }\n\n let bestFraction: { num: number; den: number; error: number } | null = null;\n\n for (const den of denominators) {\n // Find the numerator that gives the closest approximation\n const exactNum = value * den;\n const roundedNum = Math.round(exactNum);\n\n // Skip if this would give 0 numerator\n if (roundedNum === 0) continue;\n\n const approximatedValue = roundedNum / den;\n const relativeError = Math.abs(approximatedValue - value) / value;\n\n // Check if within accuracy tolerance\n if (relativeError <= accuracy) {\n // Prefer smaller denominators (they come first in the array)\n // and smaller error for same denominator\n if (!bestFraction || relativeError < bestFraction.error) {\n bestFraction = { num: roundedNum, den, error: relativeError };\n }\n }\n }\n\n if (!bestFraction) {\n return null;\n }\n\n // Simplify the fraction\n const commonDivisor = gcd(bestFraction.num, bestFraction.den);\n return {\n type: \"fraction\",\n num: bestFraction.num / commonDivisor,\n den: bestFraction.den / commonDivisor,\n };\n}\n\nexport function getNumericValue(v: DecimalValue | FractionValue): number {\n if (v.type === \"decimal\") {\n return v.decimal;\n }\n return v.num / v.den;\n}\n\nexport function multiplyNumericValue(\n v: DecimalValue | FractionValue,\n factor: number | Big,\n): DecimalValue | FractionValue {\n if (v.type === \"decimal\") {\n return {\n type: \"decimal\",\n decimal: Big(v.decimal).times(factor).toNumber(),\n };\n }\n return simplifyFraction(Big(v.num).times(factor).toNumber(), v.den);\n}\n\nexport function addNumericValues(\n val1: DecimalValue | FractionValue,\n val2: DecimalValue | FractionValue,\n): DecimalValue | FractionValue {\n let num1: number;\n let den1: number;\n let num2: number;\n let den2: number;\n\n if (val1.type === \"decimal\") {\n num1 = val1.decimal;\n den1 = 1;\n } else {\n num1 = val1.num;\n den1 = val1.den;\n }\n\n if (val2.type === \"decimal\") {\n num2 = val2.decimal;\n den2 = 1;\n } else {\n num2 = val2.num;\n den2 = val2.den;\n }\n\n // Return 0 if both values are 0\n if (num1 === 0 && num2 === 0) {\n return { type: \"decimal\", decimal: 0 };\n }\n\n // We only return a fraction where both input values are fractions themselves or only one while the other is 0\n if (\n (val1.type === \"fraction\" && val2.type === \"fraction\") ||\n (val1.type === \"fraction\" &&\n val2.type === \"decimal\" &&\n val2.decimal === 0) ||\n (val2.type === \"fraction\" && val1.type === \"decimal\" && val1.decimal === 0)\n ) {\n const commonDen = den1 * den2;\n const sumNum = num1 * den2 + num2 * den1;\n return simplifyFraction(sumNum, commonDen);\n } else {\n return {\n type: \"decimal\",\n decimal: Big(num1).div(den1).add(Big(num2).div(den2)).toNumber(),\n };\n }\n}\n\n/**\n * Rounds a numeric value to the specified number of significant digits.\n * If the integer part has 4+ digits, preserves the full integer (rounds to nearest integer).\n * @param v - The value to round (decimal or fraction)\n * @param precision - Number of significant digits (default 3)\n * @returns A DecimalValue with the rounded result\n */\nexport const toRoundedDecimal = (\n v: DecimalValue | FractionValue,\n precision: number = 3,\n): DecimalValue => {\n const value = v.type === \"decimal\" ? v.decimal : v.num / v.den;\n\n // Handle zero specially\n if (value === 0) {\n return { type: \"decimal\", decimal: 0 };\n }\n\n const absValue = Math.abs(value);\n\n // If integer part has 4+ digits, round to nearest integer\n if (absValue >= 1000) {\n return { type: \"decimal\", decimal: Math.round(value) };\n }\n\n // Calculate the order of magnitude for significant digits\n const magnitude = Math.floor(Math.log10(absValue));\n const scale = Math.pow(10, precision - 1 - magnitude);\n const rounded = Math.round(value * scale) / scale;\n\n return { type: \"decimal\", decimal: rounded };\n};\n\n/**\n * Formats a numeric value for output, using fractions if the unit supports them\n * and the value can be well-approximated as a fraction, otherwise as a rounded decimal.\n *\n * @param value - The decimal value to format\n * @param unitDef - The unit definition (to check fraction config)\n * @param precision - Number of significant digits for decimal rounding (default 3)\n * @returns A DecimalValue or FractionValue\n */\nexport const formatOutputValue = (\n value: number,\n unitDef: UnitDefinition,\n precision: number = 3,\n): DecimalValue | FractionValue => {\n // Check if unit has fractions enabled\n if (unitDef.fractions?.enabled) {\n const denominators = unitDef.fractions.denominators ?? DEFAULT_DENOMINATORS;\n const maxWhole = unitDef.fractions.maxWhole ?? DEFAULT_MAX_WHOLE;\n\n const fraction = approximateFraction(\n value,\n denominators,\n DEFAULT_FRACTION_ACCURACY,\n maxWhole,\n );\n if (fraction) {\n return fraction;\n }\n }\n\n // Fall back to rounded decimal\n return toRoundedDecimal({ type: \"decimal\", decimal: value }, precision);\n};\n\nexport function multiplyQuantityValue(\n value: FixedValue | Range,\n factor: number | Big,\n): FixedValue | Range {\n if (value.type === \"fixed\") {\n const newValue = multiplyNumericValue(\n value.value as DecimalValue | FractionValue,\n Big(factor),\n );\n if (\n newValue.type === \"fraction\" &&\n (Big(factor).toNumber() === parseInt(Big(factor).toString()) || // e.g. 2 === int\n Big(1).div(factor).toNumber() ===\n parseInt(Big(1).div(factor).toString())) // e.g. 0.25 => 4 === int\n ) {\n // Preserve fractions\n return {\n type: \"fixed\",\n value: newValue,\n };\n }\n // We might multiply with big decimal number so rounding into decimal value\n return {\n type: \"fixed\",\n value: toRoundedDecimal(newValue),\n };\n }\n\n return {\n type: \"range\",\n min: multiplyNumericValue(value.min, factor),\n max: multiplyNumericValue(value.max, factor),\n };\n}\n\nexport function getAverageValue(q: FixedValue | Range): string | number {\n if (q.type === \"fixed\") {\n return q.value.type === \"text\" ? q.value.text : getNumericValue(q.value);\n } else {\n return (getNumericValue(q.min) + getNumericValue(q.max)) / 2;\n }\n}\n","import type {\n SpecificUnitSystem,\n UnitDefinition,\n UnitDefinitionLike,\n} from \"../types\";\n\n/**\n * Check if two unit-like objects are compatible for grouping.\n * Uses strict matching: same name OR (same type AND same system).\n * This is used for shopping list grouping where we don't want to\n * auto-merge different measurement systems.\n */\nexport function areUnitsGroupable(\n u1: UnitDefinitionLike,\n u2: UnitDefinitionLike,\n): boolean {\n if (u1.name === u2.name) {\n return true;\n }\n if (u1.type === \"other\" || u2.type === \"other\") {\n return false;\n }\n // Same type AND same system (or both ambiguous)\n if (u1.type === u2.type && u1.system === u2.system) {\n return true;\n }\n // Ambiguous units are compatible with units from systems they support\n // For grouping purposes, we treat ambiguous as compatible with metric (default) only if they have a metric definition\n if (u1.type === u2.type) {\n // Ambiguous units are compatible with metric ONLY if they have a metric definition\n if (\n u1.system === \"ambiguous\" &&\n u2.system === \"metric\" &&\n u1.toBaseBySystem?.metric !== undefined\n ) {\n return true;\n }\n if (\n u2.system === \"ambiguous\" &&\n u1.system === \"metric\" &&\n u2.toBaseBySystem?.metric !== undefined\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Check if two units are convertible for addition/conversion.\n * Units are convertible if:\n * - They have the same name, OR\n * - They have the same type (regardless of system - cross-system conversion is allowed)\n *\n * @param u1 - First unit definition\n * @param u2 - Second unit definition\n * @returns true if the units can be added/converted\n */\nexport function areUnitsConvertible(\n u1: UnitDefinition,\n u2: UnitDefinition,\n): boolean {\n if (u1.name === u2.name) return true;\n if (u1.type === \"other\" || u2.type === \"other\") return false;\n // Same type = compatible (cross-system conversion is allowed)\n return u1.type === u2.type;\n}\n\n/**\n * Check if a unit is compatible with a given system.\n * - Metric units are compatible with metric\n * - Ambiguous units are compatible if they have toBaseBySystem entry for the system\n * - Units of the specified system are always compatible\n */\nexport function isUnitCompatibleWithSystem(\n unit: UnitDefinition,\n system: SpecificUnitSystem,\n): boolean {\n if (unit.system === system) return true;\n if (unit.system === \"ambiguous\") {\n // Ambiguous units with toBaseBySystem are compatible only with systems they support\n /* v8 ignore else -- @preserve */\n if (unit.toBaseBySystem) {\n return system in unit.toBaseBySystem;\n }\n // Ambiguous units without specific system support are compatible with metric by default\n /* v8 ignore next -- @preserve: defensive fallback for ambiguous units without toBaseBySystem */\n if (system === \"metric\") return true;\n }\n /* v8 ignore else -- @preserve */\n if (unit.system === \"metric\" && system === \"JP\") {\n return true;\n }\n return false;\n}\n","import { IngredientFlag, CookwareFlag, NoProductMatchErrorCode } from \"./types\";\n\nexport class ReferencedItemCannotBeRedefinedError extends Error {\n constructor(\n item_type: \"ingredient\" | \"cookware\",\n item_name: string,\n new_modifier: IngredientFlag | CookwareFlag,\n ) {\n super(\n `The referenced ${item_type} \"${item_name}\" cannot be redefined as ${new_modifier}.\nYou can either remove the reference to create a new ${item_type} defined as ${new_modifier} or add the ${new_modifier} flag to the original definition of the ${item_type}`,\n );\n this.name = \"ReferencedItemCannotBeRedefinedError\";\n }\n}\n\n/**\n * Error thrown when trying to build a shopping cart without a product catalog\n * @category Errors\n */\nexport class NoProductCatalogForCartError extends Error {\n constructor() {\n super(\n `Cannot build a cart without a product catalog. Please set one using setProductCatalog()`,\n );\n this.name = \"NoProductCatalogForCartError\";\n }\n}\n\n/**\n * Error thrown when trying to build a shopping cart without a shopping list\n * @category Errors\n */\nexport class NoShoppingListForCartError extends Error {\n constructor() {\n super(\n `Cannot build a cart without a shopping list. Please set one using setShoppingList()`,\n );\n this.name = \"NoShoppingListForCartError\";\n }\n}\n\nexport class NoProductMatchError extends Error {\n code: NoProductMatchErrorCode;\n\n constructor(item_name: string, code: NoProductMatchErrorCode) {\n const messageMap: Record<NoProductMatchErrorCode, string> = {\n incompatibleUnits: `The units of the products in the catalogue are incompatible with ingredient ${item_name} in the shopping list.`,\n noProduct:\n \"No product was found linked to ingredient name ${item_name} in the shopping list\",\n textValue: `Ingredient ${item_name} has a text value as quantity and can therefore not be matched with any product in the catalogue.`,\n noQuantity: `Ingredient ${item_name} has no quantity and can therefore not be matched with any product in the catalogue.`,\n textValue_incompatibleUnits: `Multiple alternative quantities were provided for ingredient ${item_name} in the shopping list but they were either text values or no product in catalog were found to have compatible units`,\n };\n super(messageMap[code]);\n this.code = code;\n this.name = \"NoProductMatchError\";\n }\n}\n\nexport class InvalidProductCatalogFormat extends Error {\n constructor() {\n super(\"Invalid product catalog format.\");\n this.name = \"InvalidProductCatalogFormat\";\n }\n}\n\nexport class CannotAddTextValueError extends Error {\n constructor() {\n super(\"Cannot add a quantity with a text value.\");\n this.name = \"CannotAddTextValueError\";\n }\n}\n\nexport class IncompatibleUnitsError extends Error {\n constructor(unit1: string, unit2: string) {\n super(\n `Cannot add quantities with incompatible or unknown units: ${unit1} and ${unit2}`,\n );\n this.name = \"IncompatibleUnitsError\";\n }\n}\n\nexport class InvalidQuantityFormat extends Error {\n constructor(value: string, extra?: string) {\n super(\n `Invalid quantity format found in: ${value}${extra ? ` (${extra})` : \"\"}`,\n );\n this.name = \"InvalidQuantityFormat\";\n }\n}\n\nexport class NoTabAsIndentError extends Error {\n constructor() {\n super(\n `Tabs are not allowed for indentation in metadata blocks. Please use spaces only.`,\n );\n this.name = \"NoTabAsIndentError\";\n }\n}\n\nexport class BadIndentationError extends Error {\n constructor() {\n super(`Bad identation of a nested block. Please use spaces only.`);\n this.name = \"BadIndentationError\";\n }\n}\n","import type {\n Group,\n OrGroup,\n AndGroup,\n QuantityWithUnitLike,\n DecimalValue,\n FractionValue,\n FixedValue,\n Range,\n IngredientQuantityGroup,\n IngredientQuantityAndGroup,\n AlternativeIngredientRef,\n} from \"../types\";\n\n// Helper type-checks (as before)\nexport function isGroup(x: QuantityWithUnitLike | Group): x is Group {\n return \"and\" in x || \"or\" in x;\n}\nexport function isOrGroup(x: QuantityWithUnitLike | Group): x is OrGroup {\n return isGroup(x) && \"or\" in x;\n}\n/**\n * Type guard to check if an ingredient quantity-like object is an AND group.\n * *\n * @param x - The quantity-like entry to check\n * @returns true if this is an AND group (has `and` property)\n * @category Helpers\n *\n * @example\n * ```typescript\n * for (const entry of ingredient.quantities) {\n * if (isAndGroup(entry)) {\n * // entry.and contains the list of quantities in the AND group\n * }\n * }\n * ```\n */\nexport function isAndGroup(\n x: IngredientQuantityGroup | IngredientQuantityAndGroup,\n): x is IngredientQuantityAndGroup;\nexport function isAndGroup(x: QuantityWithUnitLike | Group): x is AndGroup;\nexport function isAndGroup(\n x:\n | QuantityWithUnitLike\n | Group\n | IngredientQuantityGroup\n | IngredientQuantityAndGroup,\n): boolean {\n return \"and\" in x;\n}\nexport function isQuantity(\n x: QuantityWithUnitLike | Group,\n): x is QuantityWithUnitLike {\n return x && typeof x === \"object\" && \"quantity\" in x;\n}\n\n/**\n * Type guard to check if an ingredient quantity entry is a simple group.\n *\n * Simple groups have a single quantity with optional unit and equivalents.\n *\n * @param entry - The quantity entry to check\n * @returns true if this is a simple group (has `quantity` property)\n * @category Helpers\n *\n * @example\n * ```typescript\n * for (const entry of ingredient.quantities) {\n * if (isSimpleGroup(entry)) {\n * // entry.quantity is available\n * // entry.unit is available\n * }\n * }\n * ```\n */\nexport function isSimpleGroup(\n entry: IngredientQuantityGroup | IngredientQuantityAndGroup,\n): entry is IngredientQuantityGroup {\n return \"quantity\" in entry;\n}\n\nfunction isNumericValueIntegerLike(v: DecimalValue | FractionValue): boolean {\n if (v.type === \"decimal\") return Number.isInteger(v.decimal);\n // fraction: integer-like when numerator divisible by denominator\n return v.num % v.den === 0;\n}\n\nexport function isValueIntegerLike(q: FixedValue | Range): boolean {\n if (q.type === \"fixed\") {\n if (q.value.type === \"text\") return false;\n return isNumericValueIntegerLike(q.value);\n }\n // Range: integer-like when both min and max are integer-like\n return isNumericValueIntegerLike(q.min) && isNumericValueIntegerLike(q.max);\n}\n\n/**\n * Type guard to check if an ingredient quantity entry has alternatives.\n *\n * @param entry - The quantity entry to check\n * @returns true if this entry has alternatives\n * @category Helpers\n *\n * @example\n * ```typescript\n * for (const entry of ingredient.quantities) {\n * if (hasAlternatives(entry)) {\n * // entry.alternatives is available and non-empty\n * for (const alt of entry.alternatives) {\n * console.log(`Alternative ingredient index: ${alt.index}`);\n * }\n * }\n * }\n * ```\n */\nexport function hasAlternatives(\n entry: IngredientQuantityGroup | IngredientQuantityAndGroup,\n): entry is (IngredientQuantityGroup | IngredientQuantityAndGroup) & {\n alternatives: AlternativeIngredientRef[];\n} {\n return (\n \"alternatives\" in entry &&\n Array.isArray(entry.alternatives) &&\n entry.alternatives.length > 0\n );\n}\n","import type {\n FixedValue,\n Range,\n DecimalValue,\n FractionValue,\n Unit,\n UnitDefinition,\n QuantityWithPlainUnit,\n QuantityWithExtendedUnit,\n QuantityWithUnitDef,\n MaybeNestedGroup,\n MaybeNestedAndGroup,\n SpecificUnitSystem,\n} from \"../types\";\nimport { normalizeUnit, resolveUnit, isNoUnit } from \"../units/definitions\";\nimport { getToBase, findBestUnit } from \"../units/conversion\";\nimport { areUnitsConvertible } from \"../units/compatibility\";\nimport {\n addNumericValues,\n getNumericValue,\n formatOutputValue,\n getAverageValue,\n} from \"./numeric\";\nimport { CannotAddTextValueError, IncompatibleUnitsError } from \"../errors\";\nimport { isAndGroup, isOrGroup, isQuantity } from \"../utils/type_guards\";\n\n// `deNormalizeQuantity` is provided by `./math` and re-exported below.\n\nexport function extendAllUnits(\n q: QuantityWithPlainUnit | MaybeNestedGroup<QuantityWithPlainUnit>,\n): QuantityWithExtendedUnit | MaybeNestedGroup<QuantityWithExtendedUnit> {\n if (isAndGroup(q)) {\n return { and: q.and.map(extendAllUnits) };\n } else if (isOrGroup(q)) {\n return { or: q.or.map(extendAllUnits) };\n } else {\n const newQ: QuantityWithExtendedUnit = {\n quantity: q.quantity,\n };\n if (q.unit) {\n newQ.unit = { name: q.unit };\n }\n return newQ;\n }\n}\n\nexport function normalizeAllUnits(\n q: QuantityWithPlainUnit | MaybeNestedGroup<QuantityWithPlainUnit>,\n): QuantityWithUnitDef | MaybeNestedGroup<QuantityWithUnitDef> {\n if (isAndGroup(q)) {\n return { and: q.and.map(normalizeAllUnits) };\n } else if (isOrGroup(q)) {\n return { or: q.or.map(normalizeAllUnits) };\n } else {\n const newQ: QuantityWithUnitDef = {\n quantity: q.quantity,\n unit: resolveUnit(q.unit),\n };\n // If the quantity has equivalents, convert them to an OR group\n if (q.equivalents && q.equivalents.length > 0) {\n const equivalentsNormalized = q.equivalents.map((eq) =>\n normalizeAllUnits(eq),\n );\n return {\n or: [newQ, ...equivalentsNormalized] as QuantityWithUnitDef[],\n };\n }\n return newQ;\n }\n}\n\n/**\n * Get the default / neutral quantity which can be provided to addQuantity\n * for it to return the other value as result\n *\n * @return zero\n */\nexport function getDefaultQuantityValue(): FixedValue {\n return { type: \"fixed\", value: { type: \"decimal\", decimal: 0 } };\n}\n\n/**\n * Adds two quantity values together.\n *\n * - Adding two {@link FixedValue}s returns a new {@link FixedValue}.\n * - Adding a {@link Range} to any value returns a {@link Range}.\n *\n * @param v1 - The first quantity value.\n * @param v2 - The second quantity value.\n * @returns A new quantity value representing the sum.\n */\nexport function addQuantityValues(v1: FixedValue, v2: FixedValue): FixedValue;\nexport function addQuantityValues(\n v1: FixedValue | Range,\n v2: FixedValue | Range,\n): Range;\n\nexport function addQuantityValues(\n v1: FixedValue | Range,\n v2: FixedValue | Range,\n): FixedValue | Range {\n if (\n (v1.type === \"fixed\" && v1.value.type === \"text\") ||\n (v2.type === \"fixed\" && v2.value.type === \"text\")\n ) {\n throw new CannotAddTextValueError();\n }\n\n if (v1.type === \"fixed\" && v2.type === \"fixed\") {\n const res = addNumericValues(\n v1.value as DecimalValue | FractionValue,\n v2.value as DecimalValue | FractionValue,\n );\n return { type: \"fixed\", value: res };\n }\n const r1 =\n v1.type === \"range\" ? v1 : { type: \"range\", min: v1.value, max: v1.value };\n const r2 =\n v2.type === \"range\" ? v2 : { type: \"range\", min: v2.value, max: v2.value };\n const newMin = addNumericValues(\n r1.min as DecimalValue | FractionValue,\n r2.min as DecimalValue | FractionValue,\n );\n const newMax = addNumericValues(\n r1.max as DecimalValue | FractionValue,\n r2.max as DecimalValue | FractionValue,\n );\n return { type: \"range\", min: newMin, max: newMax };\n}\n\n/**\n * Adds two quantities, returning the result in the most appropriate unit.\n *\n * The \"best unit\" is selected based on:\n * 1. Filter candidates to units where `isBestUnit !== false`\n * 2. Use per-unit `maxValue` thresholds (prefer largest unit where value ≥ 1 and ≤ maxValue)\n * 3. Prefer integers in input unit family\n * 4. Prefer integers in any unit family\n * 5. If no integers, prefer smallest value in range\n *\n * @param q1 - The first quantity\n * @param q2 - The second quantity\n * @param system - Optional system context for resolving ambiguous units\n * @returns The sum of the two quantities\n */\nexport function addQuantities(\n q1: QuantityWithExtendedUnit,\n q2: QuantityWithExtendedUnit,\n system?: SpecificUnitSystem,\n): QuantityWithExtendedUnit {\n const v1 = q1.quantity;\n const v2 = q2.quantity;\n // Case 1: one of the two values is a text, we throw an error we can catch on the other end\n if (\n (v1.type === \"fixed\" && v1.value.type === \"text\") ||\n (v2.type === \"fixed\" && v2.value.type === \"text\")\n ) {\n throw new CannotAddTextValueError();\n }\n\n const unit1Def = normalizeUnit(q1.unit?.name);\n const unit2Def = normalizeUnit(q2.unit?.name);\n\n const addQuantityValuesAndSetUnit = (\n val1: FixedValue | Range,\n val2: FixedValue | Range,\n unit?: Unit,\n ): QuantityWithExtendedUnit => ({\n quantity: addQuantityValues(val1, val2),\n unit,\n });\n\n // Case 2: one of the two values doesn't have a unit, we preserve its value and consider its unit to be that of the other one\n // If at least one of the two units is \"\", this preserves it versus setting the resulting unit as undefined\n if (\n (q1.unit?.name === \"\" || q1.unit === undefined) &&\n q2.unit !== undefined\n ) {\n return addQuantityValuesAndSetUnit(v1, v2, q2.unit); // Prefer q2's unit\n }\n if (\n (q2.unit?.name === \"\" || q2.unit === undefined) &&\n q1.unit !== undefined\n ) {\n return addQuantityValuesAndSetUnit(v1, v2, q1.unit); // Prefer q1's unit\n }\n\n // Case 3: the two quantities have the exact same unit (or both no unit)\n if (!q1.unit && !q2.unit) {\n return addQuantityValuesAndSetUnit(v1, v2, q1.unit);\n }\n if (\n q1.unit &&\n q2.unit &&\n q1.unit.name.toLowerCase() === q2.unit.name.toLowerCase()\n ) {\n // Same unit - check if we should upgrade to a larger unit (e.g., 1200g → 1.2kg)\n if (unit1Def) {\n // Known unit type - use findBestUnit to potentially upgrade\n const effectiveSystem =\n system ??\n ([\"metric\", \"JP\"].includes(unit1Def.system)\n ? (unit1Def.system as \"metric\" | \"JP\")\n : \"US\");\n return addAndFindBestUnit(v1, v2, unit1Def, unit1Def, effectiveSystem, [\n unit1Def,\n ]);\n }\n // Unknown unit type - just add values, keep unit\n return addQuantityValuesAndSetUnit(v1, v2, q1.unit);\n }\n\n // Case 4: the two quantities have different units of known type\n if (unit1Def && unit2Def) {\n // Throw error if units aren't convertible (not of same type)\n if (!areUnitsConvertible(unit1Def, unit2Def)) {\n throw new IncompatibleUnitsError(\n `${unit1Def.type} (${q1.unit?.name})`,\n `${unit2Def.type} (${q2.unit?.name})`,\n );\n }\n\n // Determine the effective system for conversion of ambiguous units\n let effectiveSystem = system;\n\n // If no system provided, infer based on the input units:\n // 1. Prefer metric if either unit is metric\n // 2. If both are ambiguous and US-compatible, use US\n // 3. Default to metric\n // v8 ignore else -- @preserve\n if (!effectiveSystem) {\n if (unit1Def.system === \"metric\" || unit2Def.system === \"metric\") {\n effectiveSystem = \"metric\";\n } else {\n // TODO remove if v8 marker if JP is augmented with more than one unit */\n // v8 ignore if -- @preserve\n if (unit1Def.system === \"JP\" && unit2Def.system === \"JP\") {\n effectiveSystem = \"JP\";\n } else {\n // Check if both units are US-compatible\n const unit1SupportsUS =\n unit1Def.system === \"US\" ||\n (unit1Def.system === \"ambiguous\" &&\n unit1Def.toBaseBySystem &&\n \"US\" in unit1Def.toBaseBySystem);\n const unit2SupportsUS =\n unit2Def.system === \"US\" ||\n (unit2Def.system === \"ambiguous\" &&\n unit2Def.toBaseBySystem &&\n \"US\" in unit2Def.toBaseBySystem);\n effectiveSystem =\n unit1SupportsUS && unit2SupportsUS ? \"US\" : \"metric\";\n }\n }\n }\n\n return addAndFindBestUnit(v1, v2, unit1Def, unit2Def, effectiveSystem, [\n unit1Def,\n unit2Def,\n ]);\n }\n\n // Case 5: the two quantities have different units of unknown type\n throw new IncompatibleUnitsError(\n q1.unit?.name as string,\n q2.unit?.name as string,\n );\n}\n\n/**\n * Helper function to add two quantities and find the best unit for the result.\n */\nfunction addAndFindBestUnit(\n v1: FixedValue | Range,\n v2: FixedValue | Range,\n unit1Def: UnitDefinition,\n unit2Def: UnitDefinition,\n system: SpecificUnitSystem,\n inputUnits: UnitDefinition[],\n): QuantityWithExtendedUnit {\n // Convert both values to base units and sum\n const toBase1 = getToBase(unit1Def, system);\n const toBase2 = getToBase(unit2Def, system);\n\n // Get the sum in base units\n let sumInBase: number;\n if (v1.type === \"fixed\" && v2.type === \"fixed\") {\n const val1 = getNumericValue(v1.value as DecimalValue | FractionValue);\n const val2 = getNumericValue(v2.value as DecimalValue | FractionValue);\n sumInBase = val1 * toBase1 + val2 * toBase2;\n } else {\n // Handle ranges by using average for best unit selection\n const avg1 = getAverageValue(v1) as number;\n const avg2 = getAverageValue(v2) as number;\n sumInBase = avg1 * toBase1 + avg2 * toBase2;\n }\n\n // Find the best unit\n const { unit: bestUnit, value: bestValue } = findBestUnit(\n sumInBase,\n unit1Def.type,\n system,\n inputUnits,\n );\n\n // Format the value (uses fractions if unit supports them)\n const formattedValue = formatOutputValue(bestValue, bestUnit);\n\n // Handle ranges: scale the range to the best unit\n if (v1.type === \"range\" || v2.type === \"range\") {\n const r1 =\n v1.type === \"range\"\n ? v1\n : { type: \"range\" as const, min: v1.value, max: v1.value };\n const r2 =\n v2.type === \"range\"\n ? v2\n : { type: \"range\" as const, min: v2.value, max: v2.value };\n\n const minInBase =\n getNumericValue(r1.min as DecimalValue | FractionValue) * toBase1 +\n getNumericValue(r2.min as DecimalValue | FractionValue) * toBase2;\n const maxInBase =\n getNumericValue(r1.max as DecimalValue | FractionValue) * toBase1 +\n getNumericValue(r2.max as DecimalValue | FractionValue) * toBase2;\n\n const bestToBase = getToBase(bestUnit, system);\n const minValue = minInBase / bestToBase;\n const maxValue = maxInBase / bestToBase;\n\n return {\n quantity: {\n type: \"range\",\n min: formatOutputValue(minValue, bestUnit),\n max: formatOutputValue(maxValue, bestUnit),\n },\n unit: { name: bestUnit.name },\n };\n }\n\n return {\n quantity: { type: \"fixed\", value: formattedValue },\n unit: { name: bestUnit.name },\n };\n}\n\n/**\n * Converts a quantity to the best unit in a target system.\n * Returns the converted quantity, or undefined if the unit type is \"other\" or not convertible.\n *\n * @category Helpers\n *\n * @param quantity - The quantity to convert\n * @param system - The target unit system\n * @returns The converted quantity, or undefined if conversion not possible\n */\n\nexport function convertQuantityToSystem(\n quantity: QuantityWithPlainUnit,\n system: SpecificUnitSystem,\n): QuantityWithPlainUnit | undefined;\nexport function convertQuantityToSystem(\n quantity: QuantityWithExtendedUnit,\n system: SpecificUnitSystem,\n): QuantityWithExtendedUnit | undefined;\nexport function convertQuantityToSystem(\n quantity: QuantityWithPlainUnit | QuantityWithExtendedUnit,\n system: SpecificUnitSystem,\n): QuantityWithPlainUnit | QuantityWithExtendedUnit | undefined {\n const unitDef = resolveUnit(\n typeof quantity.unit === \"string\" ? quantity.unit : quantity.unit?.name,\n );\n\n // Cannot convert \"other\" type units or units without toBase\n if (unitDef.type === \"other\" || !(\"toBase\" in unitDef)) {\n return undefined;\n }\n\n const avgValue = getAverageValue(quantity.quantity);\n if (typeof avgValue !== \"number\") {\n return undefined;\n }\n\n const toBase = getToBase(unitDef, system);\n const valueInBase = avgValue * toBase;\n const { unit: bestUnit, value: bestValue } = findBestUnit(\n valueInBase,\n unitDef.type,\n system,\n [unitDef],\n );\n\n // Format the value (uses fractions if unit supports them)\n const formattedValue = formatOutputValue(bestValue, bestUnit);\n\n // Handle ranges\n if (quantity.quantity.type === \"range\") {\n const bestToBase = getToBase(bestUnit, system);\n\n const minValue =\n (getNumericValue(quantity.quantity.min) * toBase) / bestToBase;\n const maxValue =\n (getNumericValue(quantity.quantity.max) * toBase) / bestToBase;\n\n return {\n quantity: {\n type: \"range\",\n min: formatOutputValue(minValue, bestUnit),\n max: formatOutputValue(maxValue, bestUnit),\n },\n unit: { name: bestUnit.name },\n };\n }\n\n return {\n quantity: { type: \"fixed\", value: formattedValue },\n unit: { name: bestUnit.name },\n };\n}\n\nexport function toPlainUnit(\n quantity:\n | QuantityWithExtendedUnit\n | MaybeNestedGroup<QuantityWithExtendedUnit>,\n): QuantityWithPlainUnit | MaybeNestedGroup<QuantityWithPlainUnit> {\n if (isQuantity(quantity))\n return quantity.unit\n ? { ...quantity, unit: quantity.unit.name }\n : (quantity as QuantityWithPlainUnit);\n else if (isOrGroup(quantity)) {\n return {\n or: quantity.or.map(toPlainUnit),\n } as MaybeNestedGroup<QuantityWithPlainUnit>;\n } else {\n return {\n and: quantity.and.map(toPlainUnit),\n } as MaybeNestedGroup<QuantityWithPlainUnit>;\n }\n}\n\n// Convert plain unit to extended unit format for addEquivalentsAndSimplify\n// Overloads for precise return types\nexport function toExtendedUnit(\n q: QuantityWithPlainUnit,\n): QuantityWithExtendedUnit;\nexport function toExtendedUnit(\n q: MaybeNestedGroup<QuantityWithPlainUnit>,\n): MaybeNestedGroup<QuantityWithExtendedUnit>;\nexport function toExtendedUnit(\n q: QuantityWithPlainUnit | MaybeNestedGroup<QuantityWithPlainUnit>,\n): QuantityWithExtendedUnit | MaybeNestedGroup<QuantityWithExtendedUnit>;\nexport function toExtendedUnit(\n q: QuantityWithPlainUnit | MaybeNestedGroup<QuantityWithPlainUnit>,\n): QuantityWithExtendedUnit | MaybeNestedGroup<QuantityWithExtendedUnit> {\n if (isQuantity(q)) {\n return q.unit\n ? { ...q, unit: { name: q.unit } }\n : (q as QuantityWithExtendedUnit);\n } else if (isOrGroup(q)) {\n return { or: q.or.map(toExtendedUnit) };\n } else {\n return { and: q.and.map(toExtendedUnit) };\n }\n}\n\nexport function deNormalizeQuantity(\n q: QuantityWithUnitDef,\n): QuantityWithExtendedUnit {\n const result: QuantityWithExtendedUnit = {\n quantity: q.quantity,\n };\n if (!isNoUnit(q.unit)) {\n result.unit = { name: q.unit.name };\n }\n return result;\n}\n\n// Helper function to convert addEquivalentsAndSimplify result to Ingredient.quantities format\n// Returns either a QuantityWithPlainUnit (for simple/OR groups) or an IngredientQuantityAndGroup (for AND groups)\nexport const flattenPlainUnitGroup = (\n summed: QuantityWithPlainUnit | MaybeNestedGroup<QuantityWithPlainUnit>,\n): (\n | QuantityWithPlainUnit\n | {\n and: QuantityWithPlainUnit[];\n equivalents?: QuantityWithPlainUnit[];\n }\n)[] => {\n if (isOrGroup(summed)) {\n // OR group: check if first entry is an AND group (nested OR-with-AND case from addEquivalentsAndSimplify)\n // This happens when we have incompatible integer-protected primaries with compatible equivalents\n // e.g., { or: [{ and: [large, small] }, cup] }\n const entries = summed.or;\n const andGroupEntry = entries.find(\n (e): e is MaybeNestedGroup<QuantityWithPlainUnit> => isAndGroup(e),\n );\n\n if (andGroupEntry) {\n // Nested OR-with-AND case: AND group of primaries + equivalents\n const andEntries: QuantityWithPlainUnit[] = [];\n // Double casting due to:\n // - Nested ORs are flattened already\n // - Double nesting is not possible in this context\n const addGroupEntryContent = (\n andGroupEntry as MaybeNestedAndGroup<QuantityWithPlainUnit>\n ).and as QuantityWithPlainUnit[];\n for (const entry of addGroupEntryContent) {\n andEntries.push({\n quantity: entry.quantity,\n ...(entry.unit && { unit: entry.unit }),\n });\n }\n\n // The other entries in the OR group are the equivalents\n const equivalentsList: QuantityWithPlainUnit[] = entries\n .filter((e): e is QuantityWithPlainUnit => isQuantity(e))\n .map((e) => ({ quantity: e.quantity, unit: e.unit }));\n\n if (equivalentsList.length > 0) {\n return [\n {\n and: andEntries,\n equivalents: equivalentsList,\n },\n ];\n } else {\n // No equivalents: flatten to separate entries (shouldn't happen in this branch, but handle it)\n return andEntries;\n }\n }\n\n // Simple OR group: first entry is primary, rest are equivalents\n const simpleEntries = entries.filter((e): e is QuantityWithPlainUnit =>\n isQuantity(e),\n );\n /* v8 ignore else -- @preserve */\n if (simpleEntries.length > 0) {\n const result: QuantityWithPlainUnit = {\n quantity: simpleEntries[0]!.quantity,\n unit: simpleEntries[0]!.unit,\n };\n if (simpleEntries.length > 1) {\n result.equivalents = simpleEntries.slice(1);\n }\n return [result];\n }\n // Fallback: use first entry regardless\n else {\n const first = entries[0] as QuantityWithPlainUnit;\n return [{ quantity: first.quantity, unit: first.unit }];\n }\n } else if (isAndGroup(summed)) {\n // AND group: check if entries have OR groups (equivalents that can be extracted)\n const andEntries: QuantityWithPlainUnit[] = [];\n const equivalentsList: QuantityWithPlainUnit[] = [];\n for (const entry of summed.and) {\n // Double-nesting is not possible in this context\n // v8 ignore else -- @preserve\n if (isOrGroup(entry)) {\n // This entry has equivalents: first is primary, rest are equivalents\n const orEntries = entry.or as QuantityWithPlainUnit[];\n andEntries.push({\n quantity: orEntries[0]!.quantity,\n ...(orEntries[0]!.unit && { unit: orEntries[0]!.unit }),\n });\n // Collect equivalents for later merging\n equivalentsList.push(...orEntries.slice(1));\n } else if (isQuantity(entry)) {\n // Simple quantity, no equivalents\n andEntries.push({\n quantity: entry.quantity,\n ...(entry.unit && { unit: entry.unit }),\n });\n }\n }\n\n // Build the AND group result\n // If there are no equivalents, flatten to separate groupQuantity entries (water case)\n // If there are equivalents, return an AND group with the summed equivalents (carrots case)\n if (equivalentsList.length === 0) {\n // No equivalents: flatten to separate entries\n return andEntries;\n }\n\n const result: {\n and: QuantityWithPlainUnit[];\n equivalents?: QuantityWithPlainUnit[];\n } = {\n and: andEntries,\n equivalents: equivalentsList,\n };\n\n return [result];\n } else {\n // Simple QuantityWithPlainUnit\n return [\n { quantity: summed.quantity, ...(summed.unit && { unit: summed.unit }) },\n ];\n }\n};\n\n/**\n * Apply the best unit to a quantity based on its value and unit system.\n * Converts the quantity to base units, finds the best unit for display,\n * and returns a new quantity with the best unit.\n *\n * @param q - The quantity to optimize\n * @param system - The unit system to use for finding the best unit. If not provided,\n * the system is inferred from the unit (metric/JP stay as-is, others default to US).\n * @returns A new quantity with the best unit, or the original if no conversion possible\n */\nexport function applyBestUnit(\n q: QuantityWithExtendedUnit,\n system?: SpecificUnitSystem,\n): QuantityWithExtendedUnit {\n // Skip if no unit or text value\n if (!q.unit?.name) {\n return q;\n }\n\n const unitDef = resolveUnit(q.unit.name);\n\n // Skip if unit type is \"other\" (not convertible)\n if (unitDef.type === \"other\") {\n return q;\n }\n\n // Get the value - skip if text\n if (q.quantity.type === \"fixed\" && q.quantity.value.type === \"text\") {\n return q;\n }\n\n // string is filtered out in the above if\n const avgValue = getAverageValue(q.quantity) as number;\n\n // Determine effective system: use provided system, or infer from unit\n const effectiveSystem: SpecificUnitSystem =\n system ??\n ([\"metric\", \"JP\"].includes(unitDef.system)\n ? (unitDef.system as \"metric\" | \"JP\")\n : \"US\");\n\n // Convert to base units\n const toBase = getToBase(unitDef, effectiveSystem);\n const valueInBase = avgValue * toBase;\n\n // Find the best unit\n const { unit: bestUnit, value: bestValue } = findBestUnit(\n valueInBase,\n unitDef.type,\n effectiveSystem,\n [unitDef],\n );\n\n // Get canonical name of the original unit for comparison\n const originalCanonicalName = normalizeUnit(q.unit.name)?.name;\n\n // If same unit (by canonical name match), no change needed - preserve original unit name\n if (bestUnit.name === originalCanonicalName) {\n return q;\n }\n\n // Format the value for the best unit\n const formattedValue = formatOutputValue(bestValue, bestUnit);\n\n // Handle ranges: scale to the best unit\n if (q.quantity.type === \"range\") {\n const bestToBase = getToBase(bestUnit, effectiveSystem);\n const minValue = (getNumericValue(q.quantity.min) * toBase) / bestToBase;\n const maxValue = (getNumericValue(q.quantity.max) * toBase) / bestToBase;\n\n return {\n quantity: {\n type: \"range\",\n min: formatOutputValue(minValue, bestUnit),\n max: formatOutputValue(maxValue, bestUnit),\n },\n unit: { name: bestUnit.name },\n };\n }\n\n // Fixed value\n return {\n quantity: {\n type: \"fixed\",\n value: formattedValue,\n },\n unit: { name: bestUnit.name },\n };\n}\n","import type {\n MetadataExtract,\n Metadata,\n MetadataSource,\n MetadataTime,\n MetadataValue,\n MetadataObject,\n FixedValue,\n Range,\n TextValue,\n DecimalValue,\n FractionValue,\n NoteItem,\n SpecificUnitSystem,\n} from \"../types\";\nimport {\n metadataRegex,\n metadataKeyRegex,\n nestedMetaVarRegex,\n numericValueRegex,\n rangeRegex,\n numberLikeRegex,\n scalingMetaValueRegex,\n} from \"../regex\";\nimport { Section as SectionObject } from \"../classes/section\";\nimport type { Ingredient, Step, Cookware } from \"../types\";\nimport { addQuantityValues } from \"../quantities/mutations\";\nimport {\n CannotAddTextValueError,\n NoTabAsIndentError,\n BadIndentationError,\n ReferencedItemCannotBeRedefinedError,\n} from \"../errors\";\n\n/**\n * Pushes a pending note to the section content if it has items.\n * @param section - The current section object.\n * @param noteItems - The note items array.\n * @returns An empty array if the note was pushed, otherwise the original items.\n */\nexport function flushPendingNote(\n section: SectionObject,\n noteItems: NoteItem[],\n): NoteItem[] {\n if (noteItems.length > 0) {\n section.content.push({ type: \"note\", items: [...noteItems] });\n return [];\n }\n return noteItems;\n}\n\n/**\n * Pushes pending step items and a pending note to the section content.\n * @param section - The current section object.\n * @param items - The list of step items. This array will be cleared.\n * @returns true if the items were pushed, otherwise false.\n */\nexport function flushPendingItems(\n section: SectionObject,\n items: Step[\"items\"],\n): boolean {\n if (items.length > 0) {\n section.content.push({ type: \"step\", items: [...items] });\n items.length = 0;\n return true;\n }\n return false;\n}\n\n/**\n * Finds an ingredient in the list (case-insensitively) and updates it, or adds it if not present.\n * This function mutates the `ingredients` array.\n * @param ingredients - The list of ingredients.\n * @param newIngredient - The ingredient to find or add.\n * @param isReference - Whether this is a reference ingredient (`&` modifier).\n * @returns The index of the ingredient in the list.\n * @returns An object containing the index of the ingredient and its quantity part in the list.\n */\nexport function findAndUpsertIngredient(\n ingredients: Ingredient[],\n newIngredient: Ingredient,\n isReference: boolean,\n): number {\n const { name } = newIngredient;\n\n if (isReference) {\n const indexFind = ingredients.findIndex(\n (i) => i.name.toLowerCase() === name.toLowerCase(),\n );\n\n if (indexFind === -1) {\n throw new Error(\n `Referenced ingredient \"${name}\" not found. A referenced ingredient must be declared before being referenced with '&'.`,\n );\n }\n\n // Ingredient already exists\n const existingIngredient = ingredients[indexFind]!;\n\n // Checking whether any provided flags are the same as the original ingredient\n // TODO: backport fix (check on array length) to v2\n if (!newIngredient.flags) {\n if (\n Array.isArray(existingIngredient.flags) &&\n existingIngredient.flags.length > 0\n ) {\n throw new ReferencedItemCannotBeRedefinedError(\n \"ingredient\",\n existingIngredient.name,\n existingIngredient.flags[0]!,\n );\n }\n } else {\n for (const flag of newIngredient.flags) {\n /* v8 ignore else -- @preserve */\n if (\n existingIngredient.flags === undefined ||\n !existingIngredient.flags.includes(flag)\n ) {\n throw new ReferencedItemCannotBeRedefinedError(\n \"ingredient\",\n existingIngredient.name,\n flag,\n );\n }\n }\n }\n\n return indexFind;\n }\n\n // Not a reference, so add as a new ingredient.\n return ingredients.push(newIngredient) - 1;\n}\n\nexport function findAndUpsertCookware(\n cookware: Cookware[],\n newCookware: Cookware,\n isReference: boolean,\n): number {\n const { name, quantity } = newCookware;\n\n if (isReference) {\n const index = cookware.findIndex(\n (i) => i.name.toLowerCase() === name.toLowerCase(),\n );\n\n if (index === -1) {\n throw new Error(\n `Referenced cookware \"${name}\" not found. A referenced cookware must be declared before being referenced with '&'.`,\n );\n }\n\n const existingCookware = cookware[index]!;\n\n // Checking whether any provided flags are the same as the original cookware\n // TODO: backport fix (if/else) + check on array length to v2\n if (!newCookware.flags) {\n if (\n Array.isArray(existingCookware.flags) &&\n existingCookware.flags.length > 0\n ) {\n throw new ReferencedItemCannotBeRedefinedError(\n \"cookware\",\n existingCookware.name,\n existingCookware.flags[0]!,\n );\n }\n } else {\n for (const flag of newCookware.flags) {\n /* v8 ignore else -- @preserve */\n if (\n existingCookware.flags === undefined ||\n !existingCookware.flags.includes(flag)\n ) {\n throw new ReferencedItemCannotBeRedefinedError(\n \"cookware\",\n existingCookware.name,\n flag,\n );\n }\n }\n }\n\n if (quantity !== undefined) {\n if (!existingCookware.quantity) {\n existingCookware.quantity = quantity;\n } else {\n try {\n existingCookware.quantity = addQuantityValues(\n existingCookware.quantity,\n quantity,\n );\n } catch (e) {\n /* v8 ignore else -- expliciting error type -- @preserve */\n if (e instanceof CannotAddTextValueError) {\n return cookware.push(newCookware) - 1;\n }\n }\n }\n }\n return index;\n }\n\n return cookware.push(newCookware) - 1;\n}\n\n// Parser when we know the input is either a number-like value\nexport const parseFixedValue = (\n input_str: string,\n): TextValue | DecimalValue | FractionValue => {\n if (!numberLikeRegex.test(input_str)) {\n return { type: \"text\", text: input_str };\n }\n\n // After this we know that s is either a fraction or a decimal value\n const s = input_str.trim().replace(\",\", \".\");\n\n // fraction\n if (s.includes(\"/\")) {\n const parts = s.split(\"/\");\n\n const num = Number(parts[0]);\n const den = Number(parts[1]);\n\n return { type: \"fraction\", num, den };\n }\n\n // decimal\n return { type: \"decimal\", decimal: Number(s) };\n};\n\nexport function stringifyQuantityValue(quantity: FixedValue | Range): string {\n if (quantity.type === \"fixed\") {\n return stringifyFixedValue(quantity);\n } else {\n return `${stringifyFixedValue({ type: \"fixed\", value: quantity.min })}-${stringifyFixedValue({ type: \"fixed\", value: quantity.max })}`;\n }\n}\n\nfunction stringifyFixedValue(quantity: FixedValue): string {\n if (quantity.value.type === \"fraction\")\n return `${quantity.value.num}/${quantity.value.den}`;\n else if (quantity.value.type === \"decimal\")\n return String(quantity.value.decimal);\n else return quantity.value.text;\n}\n\n// TODO: rename to parseQuantityValue\nexport function parseQuantityInput(input_str: string): FixedValue | Range {\n const clean_str = String(input_str).trim();\n\n if (rangeRegex.test(clean_str)) {\n const range_parts = clean_str.split(\"-\");\n // As we've tested for it, we know that we have Number-like Quantities to parse\n const min = parseFixedValue(range_parts[0]!.trim()) as\n | DecimalValue\n | FractionValue;\n const max = parseFixedValue(range_parts[1]!.trim()) as\n | DecimalValue\n | FractionValue;\n return { type: \"range\", min, max };\n }\n\n return { type: \"fixed\", value: parseFixedValue(clean_str) };\n}\n\nexport function parseSimpleMetaVar(content: string, varName: string) {\n const varMatch = content.match(\n new RegExp(`^${varName}:\\\\s*(.*(?:\\\\r?\\\\n\\\\s+.*)*)+`, \"m\"),\n );\n return varMatch\n ? varMatch[1]?.trim().replace(/\\s*\\r?\\n\\s+/g, \" \")\n : undefined;\n}\n\nexport function parseScalingMetaVar(\n content: string,\n varName: string,\n): [number, string] | undefined {\n const varMatch = content.match(scalingMetaValueRegex(varName));\n if (!varMatch) return undefined;\n if (isNaN(Number(varMatch[2]?.trim()))) {\n throw new Error(\"Scaling variables should be numbers\");\n }\n return [Number(varMatch[2]?.trim()), varMatch[1]!.trim()];\n}\n\nexport function parseListMetaVar(content: string, varName: string) {\n // Handle both inline and YAML-style tags\n const listMatch = content.match(\n new RegExp(\n `^${varName}:\\\\s*(?:\\\\[([^\\\\]]*)\\\\]|((?:\\\\r?\\\\n\\\\s*-\\\\s*.+)+))`,\n \"m\",\n ),\n );\n if (!listMatch) return undefined;\n\n /* v8 ignore else -- @preserve */\n if (listMatch[1] !== undefined) {\n // Inline list: tags: [one, two, three]\n return listMatch[1].split(\",\").map((tag) => tag.trim());\n } else if (listMatch[2]) {\n // YAML list:\n // tags:\n // - one\n // - two\n return listMatch[2]\n .split(\"\\n\")\n .filter((line) => line.trim() !== \"\")\n .map((line) => line.replace(/^\\s*-\\s*/, \"\").trim());\n }\n}\n\n/**\n * Extracts all top-level metadata keys from frontmatter content.\n * Only captures keys at the start of a line (not nested keys).\n */\nfunction extractAllMetadataKeys(content: string): string[] {\n const keys: string[] = [];\n for (const match of content.matchAll(metadataKeyRegex)) {\n keys.push(match[1]!.trim());\n }\n return [...new Set(keys)]; // deduplicate\n}\n\n/**\n * Parses a nested YAML-style object from frontmatter content.\n * Handles indented key-value pairs under a parent key, including deeply nested objects.\n */\nexport function parseNestedMetaVar(\n content: string,\n varName: string,\n): MetadataObject | undefined {\n const match = content.match(nestedMetaVarRegex(varName));\n if (!match) return undefined;\n\n const nestedContent = match[1]!;\n return parseNestedBlock(nestedContent);\n}\n\n/**\n * Parses a block of indented YAML-like content into a nested object.\n * Recursively handles nested objects when a key has no value but has indented children.\n *\n * @remarks\n * Only spaces are allowed for indentation (tabs are rejected), following YAML spec.\n */\nexport function parseNestedBlock(content: string): MetadataObject | undefined {\n const lines = content.split(/\\r?\\n/).filter((line) => line.trim() !== \"\");\n if (lines.length === 0) return undefined;\n\n // Determine base indentation from first line (spaces only)\n const baseIndentMatch = lines[0]!.match(/^(\\s*)/);\n if (baseIndentMatch?.[0]?.includes(\"\\t\")) {\n throw new NoTabAsIndentError();\n }\n // We know that the regex will return a number of spaces (0+)\n const baseIndent = baseIndentMatch?.[1]?.length as number;\n\n // If the block itself is a list (not an object), return undefined\n // so the caller can fall through to parseListMetaVar\n if (lines[0]!.trim().startsWith(\"- \")) return undefined;\n\n const result: MetadataObject = {};\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i]!;\n\n // Check for tabs in indentation - not allowed\n const leadingWhitespace = line.match(/^(\\s*)/)?.[1];\n if (leadingWhitespace && leadingWhitespace.includes(\"\\t\")) {\n throw new NoTabAsIndentError();\n }\n\n const currentIndent = leadingWhitespace!.length;\n\n // Less indentation than base = end of this block\n if (currentIndent < baseIndent) {\n break;\n }\n\n // More indentation than base = belongs to a child (skip, handled recursively)\n if (currentIndent !== baseIndent) {\n throw new BadIndentationError();\n }\n\n // Parse key: value from this line\n const keyValueMatch = line.match(/^[ ]*([^:\\n]+?):\\s*(.*)$/);\n if (!keyValueMatch) {\n i++;\n continue;\n }\n\n const key = keyValueMatch[1]!.trim();\n const rawValue = keyValueMatch[2]!.trim();\n\n if (rawValue === \"\") {\n // Empty value means this key has nested children\n // Collect all following lines with greater indentation\n const childLines: string[] = [];\n let j = i + 1;\n while (j < lines.length) {\n const childLine = lines[j]!;\n const childIndent = childLine.match(/^([ ]*)/)?.[1]?.length;\n if (childIndent && childIndent > baseIndent) {\n childLines.push(childLine);\n j++;\n } else {\n break;\n }\n }\n\n // v8 ignore else -- @preserve\n if (childLines.length > 0) {\n // Check if children are a list (start with `-`)\n const firstChildTrimmed = childLines[0]!.trim();\n if (firstChildTrimmed.startsWith(\"- \")) {\n // Reconstruct content and reuse parseListMetaVar\n const reconstructedContent = `${key}:\\n${childLines.join(\"\\n\")}`;\n const listResult = parseListMetaVar(reconstructedContent, key);\n // v8 ignore else -- @preserve\n if (listResult) {\n result[key] = listResult.map(\n (item) => parseMetadataValue(item) as string | number,\n );\n }\n } else {\n // Parse as nested object\n const childContent = childLines.join(\"\\n\");\n const nested = parseNestedBlock(childContent);\n // v8 ignore else -- @preserve\n if (nested) {\n result[key] = nested;\n }\n }\n }\n i = j;\n } else {\n // Has a value, parse it\n result[key] = parseMetadataValue(rawValue);\n i++;\n }\n }\n\n return result;\n}\n\n/**\n * Parses a raw string value into appropriate type (number, string, or array).\n */\nfunction parseMetadataValue(rawValue: string): MetadataValue {\n // Check for inline array [a, b, c]\n if (rawValue.startsWith(\"[\") && rawValue.endsWith(\"]\")) {\n return rawValue\n .slice(1, -1)\n .split(\",\")\n .map((item) => item.trim());\n }\n\n // Check for number (integer or decimal)\n if (numericValueRegex.test(rawValue)) {\n return Number(rawValue);\n }\n\n // Return as string\n return rawValue;\n}\n\n/**\n * Detects and parses any metadata value (simple, list, nested object, or numeric).\n */\nfunction parseAnyMetaVar(\n content: string,\n varName: string,\n): MetadataValue | undefined {\n // Try nested object first (key followed by indented content)\n const nested = parseNestedMetaVar(content, varName);\n if (nested) return nested;\n\n // Try list (inline [...] or YAML-style - items)\n const list = parseListMetaVar(content, varName);\n if (list) return list;\n\n // Try simple value\n const simple = parseSimpleMetaVar(content, varName);\n if (simple) return parseMetadataValue(simple);\n\n return undefined;\n}\n\nexport function extractMetadata(content: string): MetadataExtract {\n const metadata: Metadata = {};\n let servings: number | undefined = undefined;\n\n // Is there front-matter at all?\n const metadataContent = content.match(metadataRegex)?.[2];\n if (!metadataContent) {\n return { metadata };\n }\n\n // Track keys that have been handled with special logic\n const handledKeys = new Set<string>([\n // Simple string fields\n \"title\",\n \"author\",\n \"locale\",\n \"introduction\",\n \"description\",\n \"course\",\n \"category\",\n \"diet\",\n \"cuisine\",\n \"difficulty\",\n // Source fields\n \"source\",\n \"source.name\",\n \"source.url\",\n \"source.author\",\n // Time fields\n \"prep time\",\n \"time.prep\",\n \"cook time\",\n \"time.cook\",\n \"time required\",\n \"time\",\n \"duration\",\n // Image fields\n \"image\",\n \"picture\",\n \"images\",\n \"pictures\",\n // Unit system\n \"unit system\",\n // Scaling fields\n \"servings\",\n \"yield\",\n \"serves\",\n // List fields\n \"tags\",\n ]);\n\n // Simple string metadata variables\n for (const metaVar of [\n \"title\",\n \"author\",\n \"locale\",\n \"introduction\",\n \"description\",\n \"course\",\n \"category\",\n \"diet\",\n \"cuisine\",\n \"difficulty\",\n ] as const) {\n const stringMetaValue = parseSimpleMetaVar(metadataContent, metaVar);\n if (stringMetaValue) metadata[metaVar] = stringMetaValue;\n }\n\n // Source: can be simple string, dot-notation, OR nested object\n const sourceNested = parseNestedMetaVar(metadataContent, \"source\");\n const sourceTxt = parseSimpleMetaVar(metadataContent, \"source\");\n const sourceName = parseSimpleMetaVar(metadataContent, \"source.name\");\n const sourceUrl = parseSimpleMetaVar(metadataContent, \"source.url\");\n const sourceAuthor = parseSimpleMetaVar(metadataContent, \"source.author\");\n\n if (sourceNested) {\n // YAML-style nested object\n const source: MetadataSource = {};\n // v8 ignore else -- @preserve\n if (typeof sourceNested.name === \"string\") source.name = sourceNested.name;\n // v8 ignore else -- @preserve\n if (typeof sourceNested.url === \"string\") source.url = sourceNested.url;\n // v8 ignore else -- @preserve\n if (typeof sourceNested.author === \"string\")\n source.author = sourceNested.author;\n // v8 ignore else -- @preserve\n if (Object.keys(source).length > 0) metadata.source = source;\n } else if (sourceName || sourceAuthor || sourceUrl) {\n // Dot-notation structured source\n const source: MetadataSource = {};\n if (sourceName) source.name = sourceName;\n // v8 ignore else -- @preserve\n if (sourceUrl) source.url = sourceUrl;\n if (sourceAuthor) source.author = sourceAuthor;\n metadata.source = source;\n } else if (sourceTxt) {\n // Simple string source (backwards compatible)\n metadata.source = sourceTxt;\n }\n\n // Time: can be dot-notation, legacy keys, OR nested object\n const timeNested = parseNestedMetaVar(metadataContent, \"time\");\n const prepTime =\n parseSimpleMetaVar(metadataContent, \"prep time\") ??\n parseSimpleMetaVar(metadataContent, \"time.prep\");\n const cookTime =\n parseSimpleMetaVar(metadataContent, \"cook time\") ??\n parseSimpleMetaVar(metadataContent, \"time.cook\");\n const totalTime =\n parseSimpleMetaVar(metadataContent, \"time required\") ??\n parseSimpleMetaVar(metadataContent, \"time\") ??\n parseSimpleMetaVar(metadataContent, \"duration\");\n\n if (timeNested) {\n // YAML-style nested object\n const time: MetadataTime = {};\n // v8 ignore else -- @preserve\n if (typeof timeNested.prep === \"string\") time.prep = timeNested.prep;\n // v8 ignore else -- @preserve\n if (typeof timeNested.cook === \"string\") time.cook = timeNested.cook;\n // v8 ignore else -- @preserve\n if (typeof timeNested.total === \"string\") time.total = timeNested.total;\n // v8 ignore else -- @preserve\n if (Object.keys(time).length > 0) metadata.time = time;\n } else if (prepTime || cookTime || totalTime) {\n const time: MetadataTime = {};\n if (prepTime) time.prep = prepTime;\n if (cookTime) time.cook = cookTime;\n if (totalTime) time.total = totalTime;\n metadata.time = time;\n }\n\n // Image: normalize aliases\n const image =\n parseSimpleMetaVar(metadataContent, \"image\") ??\n parseSimpleMetaVar(metadataContent, \"picture\");\n if (image) metadata.image = image;\n\n // Images: normalize aliases\n const images =\n parseListMetaVar(metadataContent, \"images\") ??\n parseListMetaVar(metadataContent, \"pictures\");\n if (images) metadata.images = images;\n\n // Unit system (case-insensitive normalization)\n let unitSystem: SpecificUnitSystem | undefined;\n const unitSystemRaw = parseSimpleMetaVar(metadataContent, \"unit system\");\n if (unitSystemRaw) {\n metadata.unitSystem = unitSystemRaw;\n const unitSystemMap: Record<string, SpecificUnitSystem> = {\n metric: \"metric\",\n us: \"US\",\n uk: \"UK\",\n jp: \"JP\",\n };\n unitSystem = unitSystemMap[unitSystemRaw.toLowerCase()];\n }\n\n // Scaling metadata variables (servings, yield, serves)\n for (const metaVar of [\"servings\", \"yield\", \"serves\"] as const) {\n const scalingMetaValue = parseScalingMetaVar(metadataContent, metaVar);\n if (scalingMetaValue && scalingMetaValue[1]) {\n metadata[metaVar] = scalingMetaValue[1];\n servings = scalingMetaValue[0];\n }\n }\n\n // Tags\n const tags = parseListMetaVar(metadataContent, \"tags\");\n if (tags) metadata.tags = tags;\n\n // Dynamic parsing: capture all additional keys not handled above\n const allKeys = extractAllMetadataKeys(metadataContent);\n for (const key of allKeys) {\n if (handledKeys.has(key)) continue;\n\n const value = parseAnyMetaVar(metadataContent, key);\n if (value !== undefined) {\n metadata[key] = value;\n }\n }\n\n return { metadata, servings, unitSystem };\n}\n\nexport function isPositiveIntegerString(str: string): boolean {\n return /^\\d+$/.test(str);\n}\n\nexport function unionOfSets<T>(s1: Set<T>, s2: Set<T>): Set<T> {\n const result = new Set(s1);\n for (const item of s2) {\n result.add(item);\n }\n return result;\n}\n\n/**\n * Returns a canonical string key from sorted alternative indices for grouping quantities.\n * Used to determine if two ingredient items have the same alternatives and can be summed together.\n * @param alternatives - Array of alternative ingredient references\n * @returns A string of sorted indices (e.g., \"0,2,5\") or null if no alternatives\n */\nexport function getAlternativeSignature(\n alternatives: { index: number }[] | undefined,\n): string | null {\n if (!alternatives || alternatives.length === 0) return null;\n return alternatives\n .map((a) => a.index)\n .sort((a, b) => a - b)\n .join(\",\");\n}\n","import type { Step, Note } from \"../types\";\n\n/**\n * Represents a recipe section\n *\n * Wrapped as a _Class_ and not defined as a simple _Type_ to expose some useful helper\n * classes (e.g. {@link Section.isBlank | isBlank()})\n *\n * @category Types\n */\nexport class Section {\n /**\n * The name of the section. Can be an empty string for the default (first) section.\n * @defaultValue `\"\"`\n */\n name: string;\n /** An array of steps and notes that make up the content of the section. */\n content: (Step | Note)[] = [];\n\n /**\n * Creates an instance of Section.\n * @param name - The name of the section. Defaults to an empty string.\n */\n constructor(name: string = \"\") {\n this.name = name;\n }\n\n /**\n * Checks if the section is blank (has no name and no content).\n * Used during recipe parsing\n * @returns `true` if the section is blank, otherwise `false`.\n */\n isBlank(): boolean {\n return this.name === \"\" && this.content.length === 0;\n }\n}\n","import { isNoUnit } from \"../units/definitions\";\nimport type {\n QuantityWithPlainUnit,\n QuantityWithExtendedUnit,\n QuantityWithUnitDef,\n UnitDefinitionLike,\n FlatOrGroup,\n MaybeNestedOrGroup,\n FlatAndGroup,\n MaybeNestedGroup,\n SpecificUnitSystem,\n} from \"../types\";\nimport { resolveUnit } from \"../units/definitions\";\nimport { multiplyQuantityValue, getAverageValue } from \"./numeric\";\nimport Big from \"big.js\";\nimport {\n isAndGroup,\n isOrGroup,\n isQuantity,\n isValueIntegerLike,\n} from \"../utils/type_guards\";\nimport {\n getDefaultQuantityValue,\n addQuantities,\n deNormalizeQuantity,\n toPlainUnit,\n} from \"./mutations\";\nimport { getUnitRatio, getBaseUnitRatio } from \"../units/conversion\";\nimport {\n findCompatibleQuantityWithinList,\n findListWithCompatibleQuantity,\n} from \"../units/lookup\";\nimport { areUnitsGroupable } from \"../units/compatibility\";\nimport { deepClone } from \"../utils/general\";\n\nexport function getEquivalentUnitsLists(\n ...quantities: (\n | QuantityWithExtendedUnit\n | FlatOrGroup<QuantityWithExtendedUnit>\n )[]\n): QuantityWithUnitDef[][] {\n const quantitiesCopy = deepClone(quantities);\n\n const OrGroups = (\n quantitiesCopy.filter(isOrGroup) as FlatOrGroup<QuantityWithExtendedUnit>[]\n ).filter((q) => q.or.length > 1);\n\n const unitLists: QuantityWithUnitDef[][] = [];\n const normalizeOrGroup = (og: FlatOrGroup<QuantityWithExtendedUnit>) => ({\n ...og,\n or: og.or.map((q) => ({\n ...q,\n unit: resolveUnit(q.unit?.name, q.unit?.integerProtected),\n })),\n });\n\n function findLinkIndexForUnits(\n lists: QuantityWithUnitDef[][],\n unitsToCheck: (UnitDefinitionLike | undefined)[],\n ) {\n return lists.findIndex((l) => {\n const listItems = l.map((q) => resolveUnit(q.unit?.name));\n return unitsToCheck.some(\n (u) => u && listItems.some((lu) => areUnitsGroupable(lu, u)),\n );\n });\n }\n\n function mergeOrGroupIntoList(\n lists: QuantityWithUnitDef[][],\n idx: number,\n og: ReturnType<typeof normalizeOrGroup>,\n ) {\n let unitRatio: Big | undefined;\n const commonUnitList = lists[idx]!.reduce((acc, v) => {\n const normalizedV: QuantityWithUnitDef = {\n ...v,\n unit: resolveUnit(v.unit?.name, v.unit?.integerProtected),\n };\n\n const commonQuantity = og.or.find(\n (q) => isQuantity(q) && areUnitsGroupable(q.unit, normalizedV.unit),\n );\n if (commonQuantity) {\n acc.push(normalizedV);\n // Only set unitRatio on the first match to avoid overwriting with\n // a less precise match (e.g., ambiguous oz matching metric cL)\n if (!unitRatio) {\n unitRatio = getUnitRatio(normalizedV, commonQuantity);\n }\n }\n return acc;\n }, [] as QuantityWithUnitDef[]);\n\n for (const newQ of og.or) {\n if (commonUnitList.some((q) => areUnitsGroupable(q.unit, newQ.unit))) {\n continue;\n } else {\n const scaledQuantity = multiplyQuantityValue(newQ.quantity, unitRatio!);\n lists[idx]!.push({ ...newQ, quantity: scaledQuantity });\n }\n }\n }\n\n for (const orGroup of OrGroups) {\n const orGroupModified = normalizeOrGroup(orGroup);\n const units = orGroupModified.or.map((q) => q.unit);\n const linkIndex = findLinkIndexForUnits(unitLists, units);\n if (linkIndex === -1) {\n unitLists.push(orGroupModified.or);\n } else {\n mergeOrGroupIntoList(unitLists, linkIndex, orGroupModified);\n }\n }\n\n return unitLists;\n}\n\n/**\n * List sorting helper for equivalent units\n * @param list - list of quantities to sort\n * @returns sorted list of quantities with integerProtected units first, then no-unit, then the rest alphabetically\n */\nexport function sortUnitList(list: QuantityWithUnitDef[]) {\n if (!list || list.length <= 1) return list;\n const priorityList: QuantityWithUnitDef[] = [];\n const nonPriorityList: QuantityWithUnitDef[] = [];\n for (const q of list) {\n if (q.unit.integerProtected || q.unit.system === \"none\") {\n priorityList.push(q);\n } else {\n nonPriorityList.push(q);\n }\n }\n\n return priorityList\n .sort((a, b) => {\n const prefixA = a.unit.integerProtected ? \"___\" : \"\";\n const prefixB = b.unit.integerProtected ? \"___\" : \"\";\n return (prefixA + a.unit.name).localeCompare(prefixB + b.unit.name, \"en\");\n })\n .concat(nonPriorityList);\n}\n\nexport function reduceOrsToFirstEquivalent(\n unitList: QuantityWithUnitDef[][],\n quantities: (\n | QuantityWithExtendedUnit\n | FlatOrGroup<QuantityWithExtendedUnit>\n )[],\n): QuantityWithExtendedUnit[] {\n function reduceToQuantity(firstQuantity: QuantityWithExtendedUnit) {\n // Look for the global list of equivalent for this quantity unit;\n const equivalentList = sortUnitList(\n findListWithCompatibleQuantity(unitList, firstQuantity)!,\n );\n if (!equivalentList) return firstQuantity;\n // Find that first quantity in the OR\n const firstQuantityInList = findCompatibleQuantityWithinList(\n equivalentList,\n firstQuantity,\n )!;\n // Normalize the first quantity's unit\n const normalizedFirstQuantity: QuantityWithUnitDef = {\n ...firstQuantity,\n unit: resolveUnit(firstQuantity.unit?.name),\n };\n // Priority 1: the first quantity has an integer-protected unit\n if (firstQuantityInList.unit.integerProtected) {\n const resultQuantity: QuantityWithExtendedUnit = {\n quantity: firstQuantity.quantity,\n };\n /* v8 ignore else -- @preserve */\n if (!isNoUnit(normalizedFirstQuantity.unit)) {\n resultQuantity.unit = { name: normalizedFirstQuantity.unit.name };\n }\n return resultQuantity;\n } else {\n // Priority 2: the next integer-protected units in the equivalent list\n let nextProtected: number | undefined;\n const equivalentListTemp = [...equivalentList];\n while (nextProtected !== -1) {\n nextProtected = equivalentListTemp.findIndex(\n (eq) => eq.unit?.integerProtected,\n );\n // Ratio between the values in the OR group vs the ones used in the equivalent unit list\n if (nextProtected !== -1) {\n const unitRatio = getUnitRatio(\n equivalentListTemp[nextProtected]!,\n firstQuantityInList,\n );\n const nextProtectedQuantityValue = multiplyQuantityValue(\n firstQuantity.quantity,\n unitRatio,\n );\n if (isValueIntegerLike(nextProtectedQuantityValue)) {\n const nextProtectedQuantity: QuantityWithExtendedUnit = {\n quantity: nextProtectedQuantityValue,\n };\n /* v8 ignore else -- @preserve */\n if (!isNoUnit(equivalentListTemp[nextProtected]!.unit)) {\n nextProtectedQuantity.unit = {\n name: equivalentListTemp[nextProtected]!.unit.name,\n };\n }\n\n return nextProtectedQuantity;\n } else {\n equivalentListTemp.splice(nextProtected, 1);\n }\n }\n }\n\n // Priority 3: the first non-integer-Protected value of the list\n const firstNonIntegerProtected = equivalentListTemp.filter(\n (q) => !q.unit.integerProtected,\n )[0]!;\n const unitRatio = getUnitRatio(\n firstNonIntegerProtected,\n firstQuantityInList,\n ).times(getBaseUnitRatio(normalizedFirstQuantity, firstQuantityInList));\n const firstEqQuantity: QuantityWithExtendedUnit = {\n quantity:\n firstNonIntegerProtected.unit.name === firstQuantity.unit!.name\n ? firstQuantity.quantity\n : multiplyQuantityValue(firstQuantity.quantity, unitRatio),\n };\n if (!isNoUnit(firstNonIntegerProtected.unit)) {\n firstEqQuantity.unit = { name: firstNonIntegerProtected.unit.name };\n }\n return firstEqQuantity;\n }\n }\n return quantities.map((q) => {\n if (isQuantity(q)) return reduceToQuantity(q);\n // Now, q is necessarily an OR group\n // We normalize units and sort them to get integerProtected elements first, then no units, then the rest\n const qListModified = sortUnitList(\n q.or.map((qq) => ({\n ...qq,\n unit: resolveUnit(qq.unit?.name, qq.unit?.integerProtected),\n })),\n );\n // We can simply use the first element\n return reduceToQuantity(qListModified[0]!);\n });\n}\n\nexport function addQuantitiesOrGroups(\n quantities: (\n | QuantityWithExtendedUnit\n | FlatOrGroup<QuantityWithExtendedUnit>\n )[],\n system?: SpecificUnitSystem,\n): {\n sum: QuantityWithUnitDef | FlatAndGroup<QuantityWithUnitDef>;\n unitsLists: QuantityWithUnitDef[][];\n} {\n if (quantities.length === 0)\n return {\n sum: {\n quantity: getDefaultQuantityValue(),\n unit: resolveUnit(),\n },\n unitsLists: [],\n };\n // This is purely theoretical and won't really happen in practice\n if (quantities.length === 1) {\n if (isQuantity(quantities[0]!))\n return {\n sum: {\n ...quantities[0],\n unit: resolveUnit(quantities[0].unit?.name),\n },\n unitsLists: [],\n };\n }\n // Step 1: find equivalents units\n const unitsLists = getEquivalentUnitsLists(...quantities);\n // Step 2: reduce the OR group to Quantities\n const reducedQuantities = reduceOrsToFirstEquivalent(unitsLists, quantities);\n // Step 3: calculate the sum\n const sum: QuantityWithUnitDef[] = [];\n for (const nextQ of reducedQuantities) {\n const existingQ = findCompatibleQuantityWithinList(sum, nextQ);\n if (existingQ === undefined) {\n sum.push({\n ...nextQ,\n unit: resolveUnit(nextQ.unit?.name),\n });\n } else {\n const sumQ = addQuantities(existingQ, nextQ, system);\n existingQ.quantity = sumQ.quantity;\n existingQ.unit = resolveUnit(sumQ.unit?.name);\n }\n }\n if (sum.length === 1) {\n return { sum: sum[0]!, unitsLists };\n }\n return { sum: { and: sum }, unitsLists };\n}\n\nexport function regroupQuantitiesAndExpandEquivalents(\n sum: QuantityWithUnitDef | FlatAndGroup<QuantityWithUnitDef>,\n unitsLists: QuantityWithUnitDef[][],\n system?: SpecificUnitSystem,\n): (QuantityWithExtendedUnit | MaybeNestedOrGroup<QuantityWithExtendedUnit>)[] {\n const sumQuantities = isAndGroup(sum) ? sum.and : [sum];\n const result: (\n | QuantityWithExtendedUnit\n | MaybeNestedOrGroup<QuantityWithExtendedUnit>\n )[] = [];\n const processedQuantities = new Set<QuantityWithUnitDef>();\n\n for (const list of unitsLists) {\n const listCopy = deepClone(list);\n const main: QuantityWithUnitDef[] = [];\n const mainCandidates = sumQuantities.filter(\n (q) => !processedQuantities.has(q),\n );\n if (mainCandidates.length === 0) continue;\n\n mainCandidates.forEach((q) => {\n // If the sum contain a value from the unit list, we push it to the mains and remove it from the list\n const mainInList = findCompatibleQuantityWithinList(listCopy, q);\n /* v8 ignore else -- @preserve */\n if (mainInList !== undefined) {\n processedQuantities.add(q);\n main.push(q);\n listCopy.splice(listCopy.indexOf(mainInList), 1);\n }\n });\n\n // We sort the equivalent units and calculate the equivalent value for each of them\n const equivalents = sortUnitList(listCopy).map((equiv) => {\n const initialValue: QuantityWithExtendedUnit = {\n quantity: getDefaultQuantityValue(),\n };\n /* v8 ignore else -- @preserve */\n if (equiv.unit) {\n initialValue.unit = { name: equiv.unit.name };\n }\n return main.reduce((acc, v) => {\n const mainInList = findCompatibleQuantityWithinList(list, v)!;\n // If the sum unit differs from the original list unit (e.g., cups → gallon after best-unit upgrade),\n // we need to convert the sum value to the equivalent amount in the original unit before scaling.\n const conversionRatio = getBaseUnitRatio(v, mainInList);\n const valueInOriginalUnit = Big(getAverageValue(v.quantity)).times(\n conversionRatio,\n );\n const newValue: QuantityWithExtendedUnit = {\n quantity: multiplyQuantityValue(\n {\n type: \"fixed\",\n value: {\n type: \"decimal\",\n decimal: valueInOriginalUnit.toNumber(),\n },\n },\n Big(getAverageValue(equiv.quantity)).div(\n getAverageValue(mainInList.quantity),\n ),\n ),\n };\n if (equiv.unit && !isNoUnit(equiv.unit)) {\n newValue.unit = { name: equiv.unit.name };\n }\n return addQuantities(acc, newValue, system);\n }, initialValue);\n });\n\n if (main.length + equivalents.length > 1) {\n const resultMain:\n | QuantityWithExtendedUnit\n | FlatAndGroup<QuantityWithExtendedUnit> =\n main.length > 1\n ? {\n and: main.map(deNormalizeQuantity),\n }\n : deNormalizeQuantity(main[0]!);\n result.push({\n or: [resultMain, ...equivalents],\n });\n }\n // Processing a UnitList with only 1 quantity is purely theoretical and won't happen in practice\n else {\n result.push(deNormalizeQuantity(main[0]!));\n }\n }\n\n // We add at the end the lone quantities\n sumQuantities\n .filter((q) => !processedQuantities.has(q))\n .forEach((q) => result.push(deNormalizeQuantity(q)));\n\n return result;\n}\n\nexport function addEquivalentsAndSimplify(\n quantities: (\n | QuantityWithExtendedUnit\n | FlatOrGroup<QuantityWithExtendedUnit>\n )[],\n system?: SpecificUnitSystem,\n): QuantityWithPlainUnit | MaybeNestedGroup<QuantityWithPlainUnit> {\n if (quantities.length === 1) {\n return toPlainUnit(quantities[0]!);\n }\n // Step 1+2+3: find equivalents, reduce groups and add quantities\n const { sum, unitsLists } = addQuantitiesOrGroups(quantities, system);\n // Step 4: regroup and expand equivalents per group\n const regrouped = regroupQuantitiesAndExpandEquivalents(\n sum,\n unitsLists,\n system,\n );\n if (regrouped.length === 1) {\n return toPlainUnit(regrouped[0]!);\n } else {\n return { and: regrouped.map(toPlainUnit) };\n }\n}\n","import type { QuantityWithExtendedUnit, QuantityWithUnitDef } from \"../types\";\nimport { resolveUnit } from \"./definitions\";\nimport { areUnitsGroupable } from \"./compatibility\";\n\nexport function findListWithCompatibleQuantity(\n list: QuantityWithUnitDef[][],\n quantity: QuantityWithExtendedUnit,\n) {\n const quantityWithUnitDef = {\n ...quantity,\n unit: resolveUnit(quantity.unit?.name),\n };\n return list.find((l) =>\n l.some((lq) => areUnitsGroupable(lq.unit, quantityWithUnitDef.unit)),\n );\n}\n\nexport function findCompatibleQuantityWithinList(\n list: QuantityWithUnitDef[],\n quantity: QuantityWithExtendedUnit,\n): QuantityWithUnitDef | undefined {\n const quantityWithUnitDef = {\n ...quantity,\n unit: resolveUnit(quantity.unit?.name),\n };\n return list.find(\n (q) =>\n q.unit.name === quantityWithUnitDef.unit.name ||\n (q.unit.type === quantityWithUnitDef.unit.type &&\n q.unit.type !== \"other\"),\n );\n}\n","const legacyDeepClone = <T>(v: T): T => {\n if (v === null || typeof v !== \"object\") {\n return v;\n }\n if (v instanceof Map) {\n return new Map(\n Array.from(v.entries()).map(([k, val]) => [\n legacyDeepClone(k),\n legacyDeepClone(val),\n ])\n ) as T;\n }\n if (v instanceof Set) {\n return new Set(Array.from(v).map((val: unknown) => legacyDeepClone(val))) as T;\n }\n if (v instanceof Date) {\n return new Date(v.getTime()) as T;\n }\n if (Array.isArray(v)) {\n return v.map((item: unknown) => legacyDeepClone(item)) as T;\n }\n const cloned = {} as Record<string, unknown>;\n for (const key of Object.keys(v)) {\n cloned[key] = legacyDeepClone((v as Record<string, unknown>)[key]);\n }\n return cloned as T;\n};\n\nexport const deepClone = <T>(v: T): T =>\n typeof structuredClone === \"function\"\n ? structuredClone(v)\n : legacyDeepClone(v);\n","import type {\n Metadata,\n Ingredient,\n IngredientExtras,\n IngredientItem,\n Timer,\n Step,\n NoteItem,\n Cookware,\n MetadataExtract,\n CookwareItem,\n IngredientFlag,\n CookwareFlag,\n RecipeAlternatives,\n IngredientAlternative,\n FlatOrGroup,\n QuantityWithExtendedUnit,\n AlternativeIngredientRef,\n QuantityWithPlainUnit,\n IngredientQuantityGroup,\n IngredientQuantityAndGroup,\n ArbitraryScalable,\n FixedNumericValue,\n StepItem,\n GetIngredientQuantitiesOptions,\n SpecificUnitSystem,\n Unit,\n MaybeScalableQuantity,\n} from \"../types\";\nimport { Section } from \"./section\";\nimport {\n tokensRegex,\n commentRegex,\n blockCommentRegex,\n metadataRegex,\n ingredientWithAlternativeRegex,\n ingredientWithGroupKeyRegex,\n ingredientAliasRegex,\n floatRegex,\n quantityAlternativeRegex,\n inlineIngredientAlternativesRegex,\n arbitraryScalableRegex,\n} from \"../regex\";\nimport {\n flushPendingItems,\n flushPendingNote,\n findAndUpsertIngredient,\n findAndUpsertCookware,\n parseQuantityInput,\n extractMetadata,\n unionOfSets,\n getAlternativeSignature,\n} from \"../utils/parser_helpers\";\nimport { addEquivalentsAndSimplify } from \"../quantities/alternatives\";\nimport { multiplyQuantityValue } from \"../quantities/numeric\";\nimport {\n toPlainUnit,\n toExtendedUnit,\n flattenPlainUnitGroup,\n convertQuantityToSystem,\n applyBestUnit,\n} from \"../quantities/mutations\";\nimport { resolveUnit } from \"../units/definitions\";\nimport { isUnitCompatibleWithSystem } from \"../units/compatibility\";\nimport Big from \"big.js\";\nimport { deepClone } from \"../utils/general\";\nimport { InvalidQuantityFormat } from \"../errors\";\n\n/**\n * Recipe parser.\n *\n * ## Usage\n *\n * You can either directly provide the recipe string when creating the instance\n * e.g. `const recipe = new Recipe('Add @eggs{3}')`, or create it first and then pass\n * the recipe string to the {@link Recipe.parse | parse()} method.\n *\n * Look at the [properties](#properties) to see how the recipe's properties are parsed.\n *\n * @category Classes\n *\n * @example\n * ```typescript\n * import { Recipe } from \"@tmlmt/cooklang-parser\";\n *\n * const recipeString = `\n * ---\n * title: Pancakes\n * tags: [breakfast, easy]\n * ---\n * Crack the @eggs{3} with @flour{100%g} and @milk{200%mL}\n *\n * Melt some @butter{50%g} in a #pan on medium heat.\n *\n * Cook for ~{5%minutes} on each side.\n * `\n * const recipe = new Recipe(recipeString);\n * ```\n */\nexport class Recipe {\n /**\n * The parsed recipe metadata.\n */\n metadata: Metadata = {};\n /**\n * The possible choices of alternative ingredients for this recipe.\n */\n choices: RecipeAlternatives = {\n ingredientItems: new Map(),\n ingredientGroups: new Map(),\n };\n /**\n * The parsed recipe ingredients.\n */\n ingredients: Ingredient[] = [];\n /**\n * The parsed recipe sections.\n */\n sections: Section[] = [];\n /**\n * The parsed recipe cookware.\n */\n cookware: Cookware[] = [];\n /**\n * The parsed recipe timers.\n */\n timers: Timer[] = [];\n /**\n * The parsed arbitrary quantities.\n */\n arbitraries: ArbitraryScalable[] = [];\n /**\n * The parsed recipe servings. Used for scaling. Parsed from one of\n * {@link Metadata.servings}, {@link Metadata.yield} or {@link Metadata.serves}\n * metadata fields.\n *\n * @see {@link Recipe.scaleBy | scaleBy()} and {@link Recipe.scaleTo | scaleTo()} methods\n */\n servings?: number;\n\n /**\n * Gets the unit system specified in the recipe metadata.\n * Used for resolving ambiguous units like tsp, tbsp, cup, etc.\n *\n * @returns The unit system if specified, or undefined to use defaults\n */\n get unitSystem(): SpecificUnitSystem | undefined {\n return Recipe.unitSystems.get(this);\n }\n\n /**\n * External storage for unit system (not a property on instances).\n * Used for resolving ambiguous units during quantity addition.\n */\n private static unitSystems = new WeakMap<Recipe, SpecificUnitSystem>();\n\n /**\n * External storage for item count (not a property on instances).\n * Used for giving ID numbers to items during parsing.\n */\n private static itemCounts = new WeakMap<Recipe, number>();\n\n /**\n * Gets the current item count for this recipe.\n */\n private getItemCount(): number {\n return Recipe.itemCounts.get(this)!;\n }\n\n /**\n * Gets the current item count and increments it.\n */\n private getAndIncrementItemCount(): number {\n const current = this.getItemCount();\n Recipe.itemCounts.set(this, current + 1);\n return current;\n }\n\n /**\n * Creates a new Recipe instance.\n * @param content - The recipe content to parse.\n */\n constructor(content?: string) {\n Recipe.itemCounts.set(this, 0);\n if (content) {\n this.parse(content);\n }\n }\n\n /**\n * Parses a matched arbitrary scalable quantity and adds it to the given array.\n * @private\n * @param regexMatchGroups - The regex match groups from arbitrary scalable regex.\n * @param intoArray - The array to push the parsed arbitrary scalable item into.\n */\n private _parseArbitraryScalable(\n regexMatchGroups: RegExpMatchArray[\"groups\"],\n intoArray: Array<NoteItem | StepItem>,\n ): void {\n // Type-guard to ensure regexMatchGroups is defined, which it is when calling this function\n // v8 ignore if -- @preserve\n if (!regexMatchGroups || !regexMatchGroups.arbitraryQuantity) return;\n const quantityMatch = regexMatchGroups.arbitraryQuantity\n ?.trim()\n .match(quantityAlternativeRegex);\n // Type-guard to ensure quantityMatch.groups is defined, which we know when calling this function\n // v8 ignore else -- @preserve\n if (quantityMatch?.groups) {\n const value = parseQuantityInput(quantityMatch.groups.quantity!);\n const unit = quantityMatch.groups.unit;\n const name = regexMatchGroups.arbitraryName || undefined;\n if (!value || (value.type === \"fixed\" && value.value.type === \"text\")) {\n throw new InvalidQuantityFormat(\n regexMatchGroups.arbitraryQuantity?.trim(),\n \"Arbitrary quantities must have a numerical value\",\n );\n }\n const arbitrary: ArbitraryScalable = {\n quantity: value as FixedNumericValue,\n };\n if (name) arbitrary.name = name;\n if (unit) arbitrary.unit = unit;\n intoArray.push({\n type: \"arbitrary\",\n index: this.arbitraries.push(arbitrary) - 1,\n });\n }\n }\n\n /**\n * Parses text for arbitrary scalables and returns NoteItem array.\n * @param text - The text to parse for arbitrary scalables.\n * @returns Array of NoteItem (text and arbitrary scalable items).\n */\n private _parseNoteText(text: string): NoteItem[] {\n const noteItems: NoteItem[] = [];\n let cursor = 0;\n const globalRegex = new RegExp(arbitraryScalableRegex.source, \"g\");\n\n for (const match of text.matchAll(globalRegex)) {\n const idx = match.index;\n /* v8 ignore else -- @preserve */\n if (idx > cursor) {\n noteItems.push({ type: \"text\", value: text.slice(cursor, idx) });\n }\n\n this._parseArbitraryScalable(match.groups, noteItems);\n cursor = idx + match[0].length;\n }\n\n if (cursor < text.length) {\n noteItems.push({ type: \"text\", value: text.slice(cursor) });\n }\n\n return noteItems;\n }\n\n private _parseQuantityRecursive(\n quantityRaw: string,\n ): QuantityWithExtendedUnit[] {\n let quantityMatch = quantityRaw.match(quantityAlternativeRegex);\n const quantities: QuantityWithExtendedUnit[] = [];\n while (quantityMatch?.groups) {\n const value = quantityMatch.groups.quantity\n ? parseQuantityInput(quantityMatch.groups.quantity)\n : undefined;\n const unit = quantityMatch.groups.unit;\n if (value) {\n const newQuantity: QuantityWithExtendedUnit = { quantity: value };\n if (unit) {\n if (unit.startsWith(\"=\")) {\n newQuantity.unit = {\n name: unit.substring(1),\n integerProtected: true,\n };\n } else {\n newQuantity.unit = { name: unit };\n }\n }\n quantities.push(newQuantity);\n } else {\n throw new InvalidQuantityFormat(quantityRaw);\n }\n quantityMatch = quantityMatch.groups.alternative\n ? quantityMatch.groups.alternative.match(quantityAlternativeRegex)\n : null;\n }\n return quantities;\n }\n\n private _parseIngredientWithAlternativeRecursive(\n ingredientMatchString: string,\n items: Step[\"items\"],\n ): void {\n const alternatives: IngredientAlternative[] = [];\n let testString = ingredientMatchString;\n while (true) {\n const match = testString.match(\n alternatives.length > 0\n ? inlineIngredientAlternativesRegex\n : ingredientWithAlternativeRegex,\n );\n if (!match?.groups) break;\n const groups = match.groups;\n\n // Use variables for readability\n // @<modifiers><name>{quantity%unit|altQuantities}(preparation)[note]|<altIngredients>\n let name = (groups.mIngredientName || groups.sIngredientName)!;\n\n // 1. We build up the different parts of the Ingredient object\n // Preparation\n const preparation = groups.ingredientPreparation;\n // Flags\n const modifiers = groups.ingredientModifiers;\n const reference = modifiers !== undefined && modifiers.includes(\"&\");\n const flags: IngredientFlag[] = [];\n if (modifiers !== undefined && modifiers.includes(\"?\")) {\n flags.push(\"optional\");\n }\n if (modifiers !== undefined && modifiers.includes(\"-\")) {\n flags.push(\"hidden\");\n }\n if (\n (modifiers !== undefined && modifiers.includes(\"@\")) ||\n groups.ingredientRecipeAnchor\n ) {\n flags.push(\"recipe\");\n }\n // Extras\n let extras: IngredientExtras | undefined = undefined;\n // -- if the ingredient is a recipe, we need to extract the name from the path given\n if (flags.includes(\"recipe\")) {\n extras = { path: `${name}.cook` };\n name = name.substring(name.lastIndexOf(\"/\") + 1);\n }\n // Distinguish name from display name / name alias\n const aliasMatch = name.match(ingredientAliasRegex);\n let listName, displayName: string;\n if (\n aliasMatch &&\n aliasMatch.groups!.ingredientListName!.trim().length > 0 &&\n aliasMatch.groups!.ingredientDisplayName!.trim().length > 0\n ) {\n listName = aliasMatch.groups!.ingredientListName!.trim();\n displayName = aliasMatch.groups!.ingredientDisplayName!.trim();\n } else {\n listName = name;\n displayName = name;\n }\n\n const newIngredient: Ingredient = {\n name: listName,\n };\n // Only add parameters if they are non null / non empty\n if (preparation) {\n newIngredient.preparation = preparation;\n }\n if (flags.length > 0) {\n newIngredient.flags = flags;\n }\n if (extras) {\n newIngredient.extras = extras;\n }\n\n const idxInList = findAndUpsertIngredient(\n this.ingredients,\n newIngredient,\n reference,\n );\n\n // 2. We build up the ingredient item\n // -- alternative quantities\n let itemQuantity: MaybeScalableQuantity | undefined = undefined;\n if (groups.ingredientQuantity) {\n const parsedQuantities = this._parseQuantityRecursive(\n groups.ingredientQuantity,\n );\n const [primary, ...rest] = parsedQuantities;\n if (primary) {\n itemQuantity = {\n ...primary,\n scalable: groups.ingredientQuantityModifier !== \"=\",\n };\n if (rest.length > 0) {\n itemQuantity.equivalents = rest;\n }\n }\n }\n\n const alternative: IngredientAlternative = {\n index: idxInList,\n displayName,\n };\n // Only add quantity fields and note if they exist\n const note = groups.ingredientNote?.trim();\n if (note) {\n alternative.note = note;\n }\n if (itemQuantity) {\n Object.assign(alternative, itemQuantity);\n }\n alternatives.push(alternative);\n testString = groups.ingredientAlternative || \"\";\n }\n\n // Update alternatives list of all processed ingredients\n if (alternatives.length > 1) {\n const alternativesIndexes = alternatives.map((alt) => alt.index);\n for (const ingredientIndex of alternativesIndexes) {\n const ingredient = this.ingredients[ingredientIndex];\n // In practice, the ingredient will always be found\n /* v8 ignore else -- @preserve */\n if (ingredient) {\n if (!ingredient.alternatives) {\n ingredient.alternatives = new Set(\n alternativesIndexes.filter((index) => index !== ingredientIndex),\n );\n } else {\n ingredient.alternatives = unionOfSets(\n ingredient.alternatives,\n new Set(\n alternativesIndexes.filter(\n (index) => index !== ingredientIndex,\n ),\n ),\n );\n }\n }\n }\n }\n\n const id = `ingredient-item-${this.getAndIncrementItemCount()}`;\n\n // Finalize item\n const newItem: IngredientItem = {\n type: \"ingredient\",\n id,\n alternatives,\n };\n items.push(newItem);\n\n if (alternatives.length > 1) {\n this.choices.ingredientItems.set(id, alternatives);\n }\n }\n\n private _parseIngredientWithGroupKey(\n ingredientMatchString: string,\n items: Step[\"items\"],\n ): void {\n const match = ingredientMatchString.match(ingredientWithGroupKeyRegex);\n // This is a type guard to ensure match and match.groups are defined\n /* v8 ignore if -- @preserve */\n if (!match?.groups) return;\n const groups = match.groups;\n\n // Use variables for readability\n // @|<groupKey|<modifiers><name>{quantity%unit|altQuantities}(preparation)[note]\n const groupKey = groups.gIngredientGroupKey!;\n let name = (groups.gmIngredientName || groups.gsIngredientName)!;\n\n // 1. We build up the different parts of the Ingredient object\n // Preparation\n const preparation = groups.gIngredientPreparation;\n // Flags\n const modifiers = groups.gIngredientModifiers;\n const reference = modifiers !== undefined && modifiers.includes(\"&\");\n const flags: IngredientFlag[] = [];\n if (modifiers !== undefined && modifiers.includes(\"?\")) {\n flags.push(\"optional\");\n }\n if (modifiers !== undefined && modifiers.includes(\"-\")) {\n flags.push(\"hidden\");\n }\n if (\n (modifiers !== undefined && modifiers.includes(\"@\")) ||\n groups.gIngredientRecipeAnchor\n ) {\n flags.push(\"recipe\");\n }\n // Extras\n let extras: IngredientExtras | undefined = undefined;\n // -- if the ingredient is a recipe, we need to extract the name from the path given\n if (flags.includes(\"recipe\")) {\n extras = { path: `${name}.cook` };\n name = name.substring(name.lastIndexOf(\"/\") + 1);\n }\n // Distinguish name from display name / name alias\n const aliasMatch = name.match(ingredientAliasRegex);\n let listName, displayName: string;\n if (\n aliasMatch &&\n aliasMatch.groups!.ingredientListName!.trim().length > 0 &&\n aliasMatch.groups!.ingredientDisplayName!.trim().length > 0\n ) {\n listName = aliasMatch.groups!.ingredientListName!.trim();\n displayName = aliasMatch.groups!.ingredientDisplayName!.trim();\n } else {\n listName = name;\n displayName = name;\n }\n\n const newIngredient: Ingredient = {\n name: listName,\n };\n // Only add parameters if they are non null / non empty\n if (preparation) {\n newIngredient.preparation = preparation;\n }\n if (flags.length > 0) {\n newIngredient.flags = flags;\n }\n if (extras) {\n newIngredient.extras = extras;\n }\n\n const idxInList = findAndUpsertIngredient(\n this.ingredients,\n newIngredient,\n reference,\n );\n\n // 2. We build up the ingredient item\n // -- alternative quantities\n let itemQuantity: MaybeScalableQuantity | undefined = undefined;\n if (groups.gIngredientQuantity) {\n const parsedQuantities = this._parseQuantityRecursive(\n groups.gIngredientQuantity,\n );\n const [primary, ...rest] = parsedQuantities;\n itemQuantity = {\n ...primary!, // there's necessarily a primary quantity as the match group was detected\n scalable: groups.gIngredientQuantityModifier !== \"=\",\n };\n if (rest.length > 0) {\n itemQuantity.equivalents = rest;\n }\n }\n\n const alternative: IngredientAlternative = {\n index: idxInList,\n displayName,\n };\n // Only add quantity fields if it exists\n if (itemQuantity) {\n Object.assign(alternative, itemQuantity);\n }\n\n const existingAlternatives = this.choices.ingredientGroups.get(groupKey);\n // For all alternative ingredients already processed for this group, add the new ingredient as alternative\n function upsertAlternativeToIngredient(\n ingredients: Ingredient[],\n ingredientIdx: number,\n newAlternativeIdx: number,\n ) {\n const ingredient = ingredients[ingredientIdx];\n // In practice, the ingredient will always be found\n /* v8 ignore else -- @preserve */\n if (ingredient) {\n if (ingredient.alternatives === undefined) {\n ingredient.alternatives = new Set([newAlternativeIdx]);\n } else {\n ingredient.alternatives.add(newAlternativeIdx);\n }\n }\n }\n if (existingAlternatives) {\n for (const alt of existingAlternatives) {\n upsertAlternativeToIngredient(this.ingredients, alt.index, idxInList);\n upsertAlternativeToIngredient(this.ingredients, idxInList, alt.index);\n }\n }\n const id = `ingredient-item-${this.getAndIncrementItemCount()}`;\n\n // Finalize item\n const newItem: IngredientItem = {\n type: \"ingredient\",\n id,\n group: groupKey,\n alternatives: [alternative],\n };\n items.push(newItem);\n\n // Populate or update choices\n const choiceAlternative = deepClone(alternative);\n choiceAlternative.itemId = id;\n const existingChoice = this.choices.ingredientGroups.get(groupKey);\n if (!existingChoice) {\n this.choices.ingredientGroups.set(groupKey, [choiceAlternative]);\n } else {\n existingChoice.push(choiceAlternative);\n }\n }\n\n /**\n * Populates the `quantities` property for each ingredient based on\n * how they appear in the recipe preparation. Only primary ingredients\n * get quantities populated. Primary ingredients get `usedAsPrimary: true` flag.\n *\n * For inline alternatives (e.g. `\\@a|b|c`), the first alternative is primary.\n * For grouped alternatives (e.g. `\\@|group|a`, `\\@|group|b`), the first item in the group is primary.\n *\n * Quantities are grouped by their alternative signature and summed using addEquivalentsAndSimplify.\n * @internal\n */\n private _populateIngredientQuantities(): void {\n // Reset quantities and usedAsPrimary flag\n for (const ing of this.ingredients) {\n delete ing.quantities;\n delete ing.usedAsPrimary;\n }\n\n // Get ingredients with quantities using default (no explicit choice = primary with alternatives)\n const ingredientsWithQuantities = this.getIngredientQuantities();\n\n // Track which indices have been matched (for handling duplicate names)\n const matchedIndices = new Set<number>();\n\n // Copy quantities and usedAsPrimary to this.ingredients\n // Match by finding the first ingredient with same name that hasn't been matched yet\n for (const computed of ingredientsWithQuantities) {\n const idx = this.ingredients.findIndex(\n (ing, i) => ing.name === computed.name && !matchedIndices.has(i),\n );\n matchedIndices.add(idx);\n const ing = this.ingredients[idx]!;\n if (computed.quantities) {\n ing.quantities = computed.quantities;\n }\n if (computed.usedAsPrimary) {\n ing.usedAsPrimary = true;\n }\n }\n }\n\n /**\n * Gets ingredients with their quantities populated, optionally filtered by section/step\n * and respecting user choices for alternatives.\n *\n * When no options are provided, returns all recipe ingredients with quantities\n * calculated using primary alternatives (same as after parsing).\n *\n * @param options - Options for filtering and choice selection:\n * - `section`: Filter to a specific section (Section object or 0-based index)\n * - `step`: Filter to a specific step (Step object or 0-based index)\n * - `choices`: Choices for alternative ingredients (defaults to primary)\n * @returns Array of Ingredient objects with quantities populated\n *\n * @example\n * ```typescript\n * // Get all ingredients with primary alternatives\n * const ingredients = recipe.getIngredientQuantities();\n *\n * // Get ingredients for a specific section\n * const sectionIngredients = recipe.getIngredientQuantities({ section: 0 });\n *\n * // Get ingredients with specific choices applied\n * const withChoices = recipe.getIngredientQuantities({\n * choices: { ingredientItems: new Map([['ingredient-item-2', 1]]) }\n * });\n * ```\n */\n getIngredientQuantities(\n options?: GetIngredientQuantitiesOptions,\n ): Ingredient[] {\n const { section, step, choices } = options || {};\n\n // Determine sections to process\n const sectionsToProcess =\n section !== undefined\n ? (() => {\n const idx =\n typeof section === \"number\"\n ? section\n : this.sections.indexOf(section);\n return idx >= 0 && idx < this.sections.length\n ? [this.sections[idx]!]\n : [];\n })()\n : this.sections;\n\n // Type for accumulated quantities\n type QuantityAccumulator = {\n quantities: (\n | QuantityWithExtendedUnit\n | FlatOrGroup<QuantityWithExtendedUnit>\n )[];\n alternativeQuantities: Map<\n number,\n (QuantityWithExtendedUnit | FlatOrGroup<QuantityWithExtendedUnit>)[]\n >;\n };\n\n // Map: ingredientIndex -> alternativeSignature -> accumulated data\n const ingredientGroups = new Map<\n number,\n Map<string | null, QuantityAccumulator>\n >();\n\n // Track selected ingredients (get quantities + usedAsPrimary) and all referenced ingredients\n const selectedIndices = new Set<number>();\n const referencedIndices = new Set<number>();\n\n for (const currentSection of sectionsToProcess) {\n const allSteps = currentSection.content.filter(\n (item): item is Step => item.type === \"step\",\n );\n\n // Determine steps to process\n const stepsToProcess =\n step === undefined\n ? allSteps\n : typeof step === \"number\"\n ? step >= 0 && step < allSteps.length\n ? [allSteps[step]!]\n : []\n : allSteps.includes(step)\n ? [step]\n : [];\n\n for (const currentStep of stepsToProcess) {\n for (const item of currentStep.items.filter(\n (item): item is IngredientItem => item.type === \"ingredient\",\n )) {\n const isGrouped = \"group\" in item && item.group !== undefined;\n const groupAlternatives = isGrouped\n ? this.choices.ingredientGroups.get(item.group!)\n : undefined;\n\n // Determine selection state\n let selectedAltIndex = 0;\n let isSelected = false;\n let hasExplicitChoice = false;\n\n if (isGrouped) {\n const groupChoice = choices?.ingredientGroups?.get(item.group!);\n hasExplicitChoice = groupChoice !== undefined;\n const targetIndex = groupChoice ?? 0;\n isSelected = groupAlternatives?.[targetIndex]?.itemId === item.id;\n } else {\n const itemChoice = choices?.ingredientItems?.get(item.id);\n hasExplicitChoice = itemChoice !== undefined;\n selectedAltIndex = itemChoice ?? 0;\n isSelected = true;\n }\n\n const alternative = item.alternatives[selectedAltIndex];\n if (!alternative || !isSelected) continue;\n\n selectedIndices.add(alternative.index);\n\n // Add all alternatives to referenced set (so indices remain valid in result)\n const allAlts = isGrouped ? groupAlternatives! : item.alternatives;\n for (const alt of allAlts) {\n referencedIndices.add(alt.index);\n }\n\n if (!alternative.quantity) continue;\n\n // Build quantity entry with equivalents\n const baseQty: QuantityWithExtendedUnit = {\n quantity: alternative.quantity,\n ...(alternative.unit && {\n unit: alternative.unit,\n }),\n };\n const quantityEntry = alternative.equivalents?.length\n ? { or: [baseQty, ...alternative.equivalents] }\n : baseQty;\n\n // Build alternative refs (only when no explicit choice)\n let alternativeRefs: AlternativeIngredientRef[] | undefined;\n if (!hasExplicitChoice && allAlts.length > 1) {\n alternativeRefs = allAlts\n .filter((alt) =>\n isGrouped\n ? alt.itemId !== item.id\n : alt.index !== alternative.index,\n )\n .map((otherAlt) => {\n const ref: AlternativeIngredientRef = { index: otherAlt.index };\n if (otherAlt.quantity) {\n const altQty: QuantityWithPlainUnit = {\n quantity: otherAlt.quantity,\n ...(otherAlt.unit && {\n unit: otherAlt.unit.name,\n }),\n ...(otherAlt.equivalents && {\n equivalents: otherAlt.equivalents.map(\n (eq) => toPlainUnit(eq) as QuantityWithPlainUnit,\n ),\n }),\n };\n ref.quantities = [altQty];\n }\n return ref;\n });\n }\n\n // Get or create accumulator for this ingredient/signature\n // Use unit type+system for signature only when there are alternatives,\n // so compatible units (g/kg) group together but incompatible (cup/g) stay separate\n const altIndices = getAlternativeSignature(alternativeRefs) ?? \"\";\n let signature: string | null;\n if (isGrouped) {\n const resolvedUnit = resolveUnit(alternative.unit?.name);\n signature = `group:${item.group}|${altIndices}|${resolvedUnit.type}`;\n } else if (altIndices) {\n // Has alternatives: include unit type to keep incompatible units separate\n const resolvedUnit = resolveUnit(alternative.unit?.name);\n signature = `${altIndices}|${resolvedUnit.type}}`;\n } else {\n // No alternatives: use null to allow normal summing behavior\n signature = null;\n }\n\n if (!ingredientGroups.has(alternative.index)) {\n ingredientGroups.set(alternative.index, new Map());\n }\n const groupsForIng = ingredientGroups.get(alternative.index)!;\n if (!groupsForIng.has(signature)) {\n groupsForIng.set(signature, {\n quantities: [],\n alternativeQuantities: new Map(),\n });\n }\n const group = groupsForIng.get(signature)!;\n\n group.quantities.push(quantityEntry);\n\n // Accumulate alternative quantities\n for (const ref of alternativeRefs ?? []) {\n if (!group.alternativeQuantities.has(ref.index)) {\n group.alternativeQuantities.set(ref.index, []);\n }\n for (const altQty of ref.quantities ?? []) {\n const extended = toExtendedUnit({\n quantity: altQty.quantity,\n unit: altQty.unit,\n });\n if (altQty.equivalents?.length) {\n const eqEntries: QuantityWithExtendedUnit[] = [\n extended,\n ...altQty.equivalents.map((eq) => toExtendedUnit(eq)),\n ];\n group.alternativeQuantities\n .get(ref.index)!\n .push({ or: eqEntries });\n } else {\n group.alternativeQuantities.get(ref.index)!.push(extended);\n }\n }\n }\n }\n }\n }\n\n // Build result\n const result: Ingredient[] = [];\n\n for (let index = 0; index < this.ingredients.length; index++) {\n if (!referencedIndices.has(index)) continue;\n\n const orig = this.ingredients[index]!;\n const ing: Ingredient = {\n name: orig.name,\n ...(orig.preparation && { preparation: orig.preparation }),\n ...(orig.flags && { flags: orig.flags }),\n ...(orig.extras && { extras: orig.extras }),\n };\n\n if (selectedIndices.has(index)) {\n ing.usedAsPrimary = true;\n\n const groupsForIng = ingredientGroups.get(index);\n if (groupsForIng) {\n const quantityGroups: (\n | IngredientQuantityGroup\n | IngredientQuantityAndGroup\n )[] = [];\n\n for (const [, group] of groupsForIng) {\n const summed = addEquivalentsAndSimplify(\n group.quantities,\n this.unitSystem,\n );\n const flattened = flattenPlainUnitGroup(summed);\n\n // Build alternatives from accumulated quantities\n const alternatives: AlternativeIngredientRef[] | undefined =\n group.alternativeQuantities.size > 0\n ? [...group.alternativeQuantities].map(([altIdx, altQtys]) => ({\n index: altIdx,\n ...(altQtys.length > 0 && {\n quantities: flattenPlainUnitGroup(\n addEquivalentsAndSimplify(altQtys, this.unitSystem),\n ).flatMap(\n /* v8 ignore next -- item.and branch requires complex nested AND-with-equivalents structure */\n (item) => (\"quantity\" in item ? [item] : item.and),\n ),\n }),\n }))\n : undefined;\n\n for (const gq of flattened) {\n if (\"and\" in gq) {\n quantityGroups.push({\n and: gq.and,\n ...(gq.equivalents?.length && {\n equivalents: gq.equivalents,\n }),\n ...(alternatives?.length && { alternatives }),\n });\n } else {\n quantityGroups.push({\n ...(gq as IngredientQuantityGroup),\n ...(alternatives?.length && { alternatives }),\n });\n }\n }\n }\n\n // v8 ignore else -- @preserve\n if (quantityGroups.length > 0) {\n ing.quantities = quantityGroups;\n }\n }\n }\n\n result.push(ing);\n }\n\n return result;\n }\n\n /**\n * Parses a recipe from a string.\n * @param content - The recipe content to parse.\n */\n parse(content: string) {\n // Remove noise\n const cleanContent = content\n .replace(metadataRegex, \"\")\n .replace(commentRegex, \"\")\n .replace(blockCommentRegex, \"\")\n .trim()\n .split(/\\r\\n?|\\n/);\n\n // Metadata\n const { metadata, servings, unitSystem }: MetadataExtract =\n extractMetadata(content);\n this.metadata = metadata;\n this.servings = servings;\n if (unitSystem) Recipe.unitSystems.set(this, unitSystem);\n\n // Initializing utility variables and property bearers\n let blankLineBefore = true;\n let section: Section = new Section();\n const items: Step[\"items\"] = [];\n let noteText = \"\";\n let inNote = false;\n\n // We parse content line by line\n for (const line of cleanContent) {\n // A blank line triggers flushing pending stuff\n if (line.trim().length === 0) {\n flushPendingItems(section, items);\n flushPendingNote(\n section,\n noteText ? this._parseNoteText(noteText) : [],\n );\n noteText = \"\";\n blankLineBefore = true;\n inNote = false;\n continue;\n }\n\n // New section\n if (line.startsWith(\"=\")) {\n flushPendingItems(section, items);\n flushPendingNote(\n section,\n noteText ? this._parseNoteText(noteText) : [],\n );\n noteText = \"\";\n\n if (this.sections.length === 0 && section.isBlank()) {\n section.name = line.replace(/^=+|=+$/g, \"\").trim();\n } else {\n /* v8 ignore else -- @preserve */\n if (!section.isBlank()) {\n this.sections.push(section);\n }\n section = new Section(line.replace(/^=+|=+$/g, \"\").trim());\n }\n blankLineBefore = true;\n inNote = false;\n continue;\n }\n\n // New note\n if (blankLineBefore && line.startsWith(\">\")) {\n flushPendingItems(section, items);\n noteText = line.substring(1).trim();\n inNote = true;\n blankLineBefore = false;\n continue;\n }\n\n // Continue note\n if (inNote) {\n if (line.startsWith(\">\")) {\n noteText += \" \" + line.substring(1).trim();\n } else {\n noteText += \" \" + line.trim();\n }\n blankLineBefore = false;\n continue;\n }\n\n // Detecting items\n let cursor = 0;\n for (const match of line.matchAll(tokensRegex)) {\n const idx = match.index;\n /* v8 ignore else -- @preserve */\n if (idx > cursor) {\n items.push({ type: \"text\", value: line.slice(cursor, idx) });\n }\n\n const groups = match.groups!;\n\n // Ingredient items with potential in-line alternatives\n if (groups.mIngredientName || groups.sIngredientName) {\n this._parseIngredientWithAlternativeRecursive(match[0], items);\n }\n // Ingredient items part of a group of alternative ingredients\n else if (groups.gmIngredientName || groups.gsIngredientName) {\n this._parseIngredientWithGroupKey(match[0], items);\n }\n // Cookware items\n else if (groups.mCookwareName || groups.sCookwareName) {\n const name = (groups.mCookwareName || groups.sCookwareName)!;\n const modifiers = groups.cookwareModifiers;\n const quantityRaw = groups.cookwareQuantity;\n const reference = modifiers !== undefined && modifiers.includes(\"&\");\n const flags: CookwareFlag[] = [];\n if (modifiers !== undefined && modifiers.includes(\"?\")) {\n flags.push(\"optional\");\n }\n if (modifiers !== undefined && modifiers.includes(\"-\")) {\n flags.push(\"hidden\");\n }\n const quantity = quantityRaw\n ? parseQuantityInput(quantityRaw)\n : undefined;\n const newCookware: Cookware = {\n name,\n };\n if (quantity) {\n newCookware.quantity = quantity;\n }\n if (flags.length > 0) {\n newCookware.flags = flags;\n }\n\n // Add cookware in cookware list\n const idxInList = findAndUpsertCookware(\n this.cookware,\n newCookware,\n reference,\n );\n\n // Adding the item itself in the preparation\n const newItem: CookwareItem = {\n type: \"cookware\",\n index: idxInList,\n };\n if (quantity) {\n newItem.quantity = quantity;\n }\n items.push(newItem);\n }\n // Arbitrary scalable quantities\n else if (groups.arbitraryQuantity) {\n this._parseArbitraryScalable(groups, items);\n }\n // Then it's necessarily a timer which was matched\n else {\n const durationStr = groups.timerQuantity!.trim();\n const unit = (groups.timerUnit || \"\").trim();\n if (!unit) {\n throw new Error(\"Timer missing unit\");\n }\n const name = groups.timerName || undefined;\n const duration = parseQuantityInput(durationStr);\n const timerObj: Timer = {\n name,\n duration,\n unit,\n };\n items.push({ type: \"timer\", index: this.timers.push(timerObj) - 1 });\n }\n\n cursor = idx + match[0].length;\n }\n\n if (cursor < line.length) {\n items.push({ type: \"text\", value: line.slice(cursor) });\n }\n\n blankLineBefore = false;\n }\n\n // End of content reached: pushing all temporarily saved elements\n flushPendingItems(section, items);\n flushPendingNote(section, noteText ? this._parseNoteText(noteText) : []);\n if (!section.isBlank()) {\n this.sections.push(section);\n }\n\n this._populateIngredientQuantities();\n }\n\n /**\n * Scales the recipe to a new number of servings. In practice, it calls\n * {@link Recipe.scaleBy | scaleBy} with a factor corresponding to the ratio between `newServings`\n * and the recipe's {@link Recipe.servings | servings} value.\n * @param newServings - The new number of servings.\n * @returns A new Recipe instance with the scaled ingredients.\n * @throws `Error` if the recipe does not contains an initial {@link Recipe.servings | servings} value\n */\n scaleTo(newServings: number): Recipe {\n let originalServings = this.getServings();\n\n // Default to 1 if no servings defined\n if (originalServings === undefined || originalServings === 0) {\n originalServings = 1;\n }\n\n const factor = Big(newServings).div(originalServings);\n return this.scaleBy(factor);\n }\n\n /**\n * Scales the recipe by a factor.\n * @param factor - The factor to scale the recipe by. While integers can be passed as-is, it is recommended to pass fractions as\n * [Big](https://github.com/MikeMcl/big.js/) values, e.g. `Big(num).div(den)` in order to avoid undesirable floating point operation inaccuracies.\n * @returns A new Recipe instance with the scaled ingredients.\n */\n scaleBy(factor: number | Big): Recipe {\n const newRecipe = this.clone();\n\n let originalServings = newRecipe.getServings();\n\n // Default to 1 if no servings defined\n if (originalServings === undefined || originalServings === 0) {\n originalServings = 1;\n }\n\n // Get unit system for best unit optimization (if set)\n const unitSystem = this.unitSystem;\n\n function scaleAlternativesBy(\n alternatives: IngredientAlternative[],\n factor: number | Big,\n ) {\n for (const alternative of alternatives) {\n if (alternative.quantity) {\n const scaleFactor = alternative.scalable ? Big(factor) : 1;\n // Scale the primary quantity\n if (\n alternative.quantity.type !== \"fixed\" ||\n alternative.quantity.value.type !== \"text\"\n ) {\n alternative.quantity = multiplyQuantityValue(\n alternative.quantity,\n scaleFactor,\n );\n }\n // Scale equivalents if any\n if (alternative.equivalents) {\n alternative.equivalents = alternative.equivalents.map(\n (altQuantity: QuantityWithExtendedUnit) => {\n if (\n altQuantity.quantity.type === \"fixed\" &&\n altQuantity.quantity.value.type === \"text\"\n ) {\n return altQuantity;\n } else {\n return {\n ...altQuantity,\n quantity: multiplyQuantityValue(\n altQuantity.quantity,\n scaleFactor,\n ),\n };\n }\n },\n );\n }\n\n // Apply best unit optimization (infers system from unit if unitSystem not set)\n // Apply to primary\n const optimizedPrimary = applyBestUnit(\n {\n quantity: alternative.quantity,\n unit: alternative.unit,\n },\n unitSystem,\n );\n alternative.quantity = optimizedPrimary.quantity;\n alternative.unit = optimizedPrimary.unit;\n\n // Apply to equivalents\n if (alternative.equivalents) {\n alternative.equivalents = alternative.equivalents.map((eq) =>\n applyBestUnit(eq, unitSystem),\n );\n }\n }\n }\n }\n\n // Scale IngredientItems\n for (const section of newRecipe.sections) {\n for (const step of section.content.filter(\n (item) => item.type === \"step\",\n )) {\n for (const item of step.items.filter(\n (item) => item.type === \"ingredient\",\n )) {\n scaleAlternativesBy(item.alternatives, factor);\n }\n }\n }\n\n // Scale Choices\n for (const alternatives of newRecipe.choices.ingredientGroups.values()) {\n scaleAlternativesBy(alternatives, factor);\n }\n for (const alternatives of newRecipe.choices.ingredientItems.values()) {\n scaleAlternativesBy(alternatives, factor);\n }\n\n // Scale Arbitraries\n for (const arbitrary of newRecipe.arbitraries) {\n arbitrary.quantity = multiplyQuantityValue(\n arbitrary.quantity,\n factor,\n ) as FixedNumericValue;\n }\n\n newRecipe._populateIngredientQuantities();\n\n newRecipe.servings = Big(originalServings).times(factor).toNumber();\n\n /* v8 ignore else -- @preserve */\n if (newRecipe.metadata.servings && this.metadata.servings) {\n if (\n floatRegex.test(String(this.metadata.servings).replace(\",\", \".\").trim())\n ) {\n const servingsValue = parseFloat(\n String(this.metadata.servings).replace(\",\", \".\"),\n );\n newRecipe.metadata.servings = String(\n Big(servingsValue).times(factor).toNumber(),\n );\n }\n }\n\n /* v8 ignore else -- @preserve */\n if (newRecipe.metadata.yield && this.metadata.yield) {\n if (\n floatRegex.test(String(this.metadata.yield).replace(\",\", \".\").trim())\n ) {\n const yieldValue = parseFloat(\n String(this.metadata.yield).replace(\",\", \".\"),\n );\n newRecipe.metadata.yield = String(\n Big(yieldValue).times(factor).toNumber(),\n );\n }\n }\n\n /* v8 ignore else -- @preserve */\n if (newRecipe.metadata.serves && this.metadata.serves) {\n if (\n floatRegex.test(String(this.metadata.serves).replace(\",\", \".\").trim())\n ) {\n const servesValue = parseFloat(\n String(this.metadata.serves).replace(\",\", \".\"),\n );\n newRecipe.metadata.serves = String(\n Big(servesValue).times(factor).toNumber(),\n );\n }\n }\n\n return newRecipe;\n }\n\n /**\n * Converts all ingredient quantities in the recipe to a target unit system.\n *\n * @param system - The target unit system to convert to (metric, US, UK, JP)\n * @param method - How to handle existing quantities:\n * - \"keep\": Keep all existing equivalents (swap if needed, or add converted)\n * - \"replace\": Replace primary with target system quantity, discard equivalent used for conversion\n * - \"remove\": Only keep target system quantity, delete all equivalents\n * @returns A new Recipe instance with converted quantities\n *\n * @example\n * ```typescript\n * // Convert a recipe to metric, keeping original units as equivalents\n * const metricRecipe = recipe.convertTo(\"metric\", \"keep\");\n *\n * // Convert to US units, removing all other equivalents\n * const usRecipe = recipe.convertTo(\"US\", \"remove\");\n * ```\n */\n convertTo(\n system: SpecificUnitSystem,\n method: \"keep\" | \"replace\" | \"remove\",\n ): Recipe {\n const newRecipe = this.clone();\n\n /**\n * Helper to build new primary quantity fields from a converted quantity\n */\n function buildNewPrimary(\n convertedQty: QuantityWithExtendedUnit,\n oldPrimary: QuantityWithExtendedUnit,\n remainingEquivalents: QuantityWithExtendedUnit[],\n scalable: boolean,\n integerProtected: boolean | undefined,\n source: \"converted\" | \"swapped\",\n ): MaybeScalableQuantity {\n const newUnit: Unit | undefined =\n integerProtected && convertedQty.unit\n ? { name: convertedQty.unit.name, integerProtected: true }\n : convertedQty.unit;\n\n const newPrimary: MaybeScalableQuantity = {\n quantity: convertedQty.quantity,\n unit: newUnit,\n scalable,\n };\n\n if (method === \"remove\") {\n return newPrimary;\n } else if (method === \"replace\") {\n // An equivalent was converted and replaced, we still want to keep the oldPrimary\n if (source === \"converted\") remainingEquivalents.push(oldPrimary);\n if (remainingEquivalents.length > 0) {\n // Keep remaining equivalents\n newPrimary.equivalents = remainingEquivalents;\n }\n } else {\n // method === \"keep\": include old primary + remaining equivalents\n newPrimary.equivalents = [oldPrimary, ...remainingEquivalents];\n }\n\n return newPrimary;\n }\n\n /**\n * Convert a single alternative's quantity to the target system.\n */\n function convertAlternativeQuantity(\n alternative: IngredientAlternative & MaybeScalableQuantity,\n ): MaybeScalableQuantity {\n const primaryUnit = resolveUnit(alternative.unit?.name);\n const equivalents = alternative.equivalents ?? [];\n const oldPrimary: QuantityWithExtendedUnit = {\n quantity: alternative.quantity,\n unit: alternative.unit,\n };\n\n // Check if primary is already in target system\n if (\n primaryUnit.type !== \"other\" &&\n isUnitCompatibleWithSystem(primaryUnit, system)\n ) {\n // Primary is already in target system\n if (method === \"remove\") {\n return {\n quantity: alternative.quantity,\n unit: alternative.unit,\n scalable: alternative.scalable,\n };\n }\n return {\n quantity: alternative.quantity,\n unit: alternative.unit,\n scalable: alternative.scalable,\n equivalents,\n };\n }\n\n // Look for an equivalent in the target system\n const targetEquivIndex = equivalents.findIndex((eq) => {\n const eqUnit = resolveUnit(eq.unit?.name);\n return (\n eqUnit.type !== \"other\" && isUnitCompatibleWithSystem(eqUnit, system)\n );\n });\n\n if (targetEquivIndex !== -1) {\n // Found an equivalent in target system - swap with primary\n const targetEquiv = equivalents[targetEquivIndex]!;\n const remainingEquivalents = equivalents.filter(\n (_, i) => i !== targetEquivIndex,\n );\n return buildNewPrimary(\n targetEquiv,\n oldPrimary,\n remainingEquivalents,\n alternative.scalable,\n targetEquiv.unit?.integerProtected,\n \"swapped\",\n );\n }\n\n // No equivalent in target system - try to convert from primary\n const converted = convertQuantityToSystem(oldPrimary, system);\n\n if (converted && converted.unit) {\n return buildNewPrimary(\n converted,\n oldPrimary,\n equivalents,\n alternative.scalable,\n alternative.unit?.integerProtected,\n \"swapped\",\n );\n }\n\n // Primary cannot be converted - try to convert from equivalents\n for (let i = 0; i < equivalents.length; i++) {\n const equiv = equivalents[i]!;\n const convertedEquiv = convertQuantityToSystem(equiv, system);\n\n // v8 ignore else -- @preserve\n if (convertedEquiv && convertedEquiv.unit) {\n const remainingEquivalents =\n method === \"keep\"\n ? equivalents\n : equivalents.filter((_, idx) => idx !== i);\n return buildNewPrimary(\n convertedEquiv,\n oldPrimary,\n remainingEquivalents,\n alternative.scalable,\n equiv.unit?.integerProtected,\n \"converted\",\n );\n }\n }\n\n // Cannot convert - return as-is (or with cleared equivalents for \"remove\")\n // v8 ignore next -- @preserve\n if (method === \"remove\") {\n return {\n quantity: alternative.quantity,\n unit: alternative.unit,\n scalable: alternative.scalable,\n };\n } else {\n return {\n quantity: alternative.quantity,\n unit: alternative.unit,\n scalable: alternative.scalable,\n equivalents,\n };\n }\n }\n\n /**\n * Convert all alternatives in a list\n */\n function convertAlternatives(alternatives: IngredientAlternative[]) {\n for (const alternative of alternatives) {\n // v8 ignore else -- @preserve\n if (alternative.quantity) {\n const converted = convertAlternativeQuantity(\n alternative as IngredientAlternative & MaybeScalableQuantity,\n );\n alternative.quantity = converted.quantity;\n alternative.unit = converted.unit;\n (\n alternative as IngredientAlternative & MaybeScalableQuantity\n ).scalable = converted.scalable;\n alternative.equivalents = converted.equivalents;\n }\n }\n }\n\n // Convert IngredientItems in sections\n for (const section of newRecipe.sections) {\n for (const step of section.content.filter(\n (item) => item.type === \"step\",\n )) {\n for (const item of step.items.filter(\n (item) => item.type === \"ingredient\",\n )) {\n convertAlternatives(item.alternatives);\n }\n }\n }\n\n // Convert Choices\n for (const alternatives of newRecipe.choices.ingredientGroups.values()) {\n convertAlternatives(alternatives);\n }\n for (const alternatives of newRecipe.choices.ingredientItems.values()) {\n convertAlternatives(alternatives);\n }\n\n // Re-aggregate ingredient quantities\n newRecipe._populateIngredientQuantities();\n\n // Setting the unit system in 'keep' mode will convert all equivalents to that system\n // which will lead to duplicates\n if (method !== \"keep\") Recipe.unitSystems.set(newRecipe, system);\n\n return newRecipe;\n }\n\n /**\n * Gets the number of servings for the recipe.\n * @private\n * @returns The number of servings, or undefined if not set.\n */\n private getServings(): number | undefined {\n if (this.servings) {\n return this.servings;\n }\n return undefined;\n }\n\n /**\n * Clones the recipe.\n * @returns A new Recipe instance with the same properties.\n */\n clone(): Recipe {\n const newRecipe = new Recipe();\n newRecipe.choices = deepClone(this.choices);\n Recipe.itemCounts.set(newRecipe, this.getItemCount());\n // deep copy\n newRecipe.metadata = deepClone(this.metadata);\n newRecipe.ingredients = deepClone(this.ingredients);\n newRecipe.sections = this.sections.map((section) => {\n const newSection = new Section(section.name);\n newSection.content = deepClone(section.content);\n return newSection;\n });\n newRecipe.cookware = deepClone(this.cookware);\n newRecipe.timers = deepClone(this.timers);\n newRecipe.arbitraries = deepClone(this.arbitraries);\n newRecipe.servings = this.servings;\n return newRecipe;\n }\n}\n","import { CategoryConfig } from \"./category_config\";\nimport { Recipe } from \"./recipe\";\nimport type {\n CategorizedIngredients,\n AddedRecipe,\n AddedIngredient,\n QuantityWithExtendedUnit,\n QuantityWithPlainUnit,\n MaybeNestedGroup,\n FlatOrGroup,\n AddedRecipeOptions,\n} from \"../types\";\nimport { addEquivalentsAndSimplify } from \"../quantities/alternatives\";\nimport { extendAllUnits } from \"../quantities/mutations\";\nimport { isAndGroup } from \"../utils/type_guards\";\n\n/**\n * Shopping List generator.\n *\n * ## Usage\n *\n * - Create a new ShoppingList instance with an optional category configuration (see {@link ShoppingList.\"constructor\" | constructor})\n * - Add recipes, scaling them as needed (see {@link ShoppingList.addRecipe | addRecipe()})\n * - Categorize the ingredients (see {@link ShoppingList.categorize | categorize()})\n *\n * @example\n *\n * ```typescript\n * import * as fs from \"fs\";\n * import { ShoppingList } from @tmlmt/cooklang-parser;\n *\n * const categoryConfig = fs.readFileSync(\"./myconfig.txt\", \"utf-8\")\n * const recipe1 = new Recipe(fs.readFileSync(\"./myrecipe.cook\", \"utf-8\"));\n * const shoppingList = new ShoppingList();\n * shoppingList.setCategoryConfig(categoryConfig);\n * // Quantities are automatically calculated and ingredients categorized\n * // when adding a recipe\n * shoppingList.addRecipe(recipe1);\n * ```\n *\n * @category Classes\n */\nexport class ShoppingList {\n // TODO: backport type change\n /**\n * The ingredients in the shopping list.\n */\n ingredients: AddedIngredient[] = [];\n /**\n * The recipes in the shopping list.\n */\n recipes: AddedRecipe[] = [];\n /**\n * The category configuration for the shopping list.\n */\n categoryConfig?: CategoryConfig;\n /**\n * The categorized ingredients in the shopping list.\n */\n categories?: CategorizedIngredients;\n\n /**\n * Creates a new ShoppingList instance\n * @param categoryConfigStr - The category configuration to parse.\n */\n constructor(categoryConfigStr?: string | CategoryConfig) {\n if (categoryConfigStr) {\n this.setCategoryConfig(categoryConfigStr);\n }\n }\n\n private calculateIngredients() {\n this.ingredients = [];\n\n const addIngredientQuantity = (\n name: string,\n quantityTotal:\n | QuantityWithPlainUnit\n | MaybeNestedGroup<QuantityWithPlainUnit>,\n ) => {\n const quantityTotalExtended = extendAllUnits(quantityTotal);\n const newQuantities = (\n isAndGroup(quantityTotalExtended)\n ? quantityTotalExtended.and\n : [quantityTotalExtended]\n ) as (QuantityWithExtendedUnit | FlatOrGroup<QuantityWithExtendedUnit>)[];\n const existing = this.ingredients.find((i) => i.name === name);\n\n if (existing) {\n if (!existing.quantityTotal) {\n existing.quantityTotal = quantityTotal;\n return;\n }\n try {\n const existingQuantityTotalExtended = extendAllUnits(\n existing.quantityTotal,\n );\n const existingQuantities = (\n isAndGroup(existingQuantityTotalExtended)\n ? existingQuantityTotalExtended.and\n : [existingQuantityTotalExtended]\n ) as (\n | QuantityWithExtendedUnit\n | FlatOrGroup<QuantityWithExtendedUnit>\n )[];\n existing.quantityTotal = addEquivalentsAndSimplify([\n ...existingQuantities,\n ...newQuantities,\n ]);\n return;\n } catch {\n // Incompatible\n }\n }\n\n this.ingredients.push({\n name,\n quantityTotal,\n });\n };\n\n for (const addedRecipe of this.recipes) {\n let scaledRecipe: Recipe;\n if (\"factor\" in addedRecipe) {\n const { recipe, factor } = addedRecipe;\n scaledRecipe = factor === 1 ? recipe : recipe.scaleBy(factor);\n } else {\n scaledRecipe = addedRecipe.recipe.scaleTo(addedRecipe.servings);\n }\n\n // Get computed ingredients with total quantities based on choices (or default)\n const ingredients = scaledRecipe.getIngredientQuantities({\n choices: addedRecipe.choices,\n });\n\n for (const ingredient of ingredients) {\n // Do not add hidden ingredients to the shopping list\n if (ingredient.flags && ingredient.flags.includes(\"hidden\")) {\n continue;\n }\n\n // Only add ingredients that were selected (have usedAsPrimary flag)\n // This filters out alternative ingredients that weren't chosen\n if (!ingredient.usedAsPrimary) {\n continue;\n }\n\n // Sum up quantities from the ingredient's quantity groups\n if (ingredient.quantities && ingredient.quantities.length > 0) {\n // Extract all quantities (converting to plain units for summing)\n const allQuantities: (\n | QuantityWithPlainUnit\n | MaybeNestedGroup<QuantityWithPlainUnit>\n )[] = [];\n for (const qGroup of ingredient.quantities) {\n if (\"and\" in qGroup) {\n // AndGroup - add each quantity separately\n for (const qty of qGroup.and) {\n allQuantities.push(qty);\n }\n } else {\n // Simple quantity (strip alternatives - choices already applied)\n const plainQty: QuantityWithPlainUnit = {\n quantity: qGroup.quantity,\n };\n if (qGroup.unit) plainQty.unit = qGroup.unit;\n if (qGroup.equivalents) plainQty.equivalents = qGroup.equivalents;\n allQuantities.push(plainQty);\n }\n }\n if (allQuantities.length === 1) {\n addIngredientQuantity(ingredient.name, allQuantities[0]!);\n } else {\n // allQuantities.length > 1\n // Sum up using addEquivalentsAndSimplify\n const extendedQuantities = allQuantities.map((q) =>\n extendAllUnits(q),\n );\n const totalQuantity = addEquivalentsAndSimplify(\n extendedQuantities as (\n | QuantityWithExtendedUnit\n | FlatOrGroup<QuantityWithExtendedUnit>\n )[],\n );\n // addEquivalentsAndSimplify already returns plain units\n addIngredientQuantity(ingredient.name, totalQuantity);\n }\n } else if (!this.ingredients.some((i) => i.name === ingredient.name)) {\n this.ingredients.push({ name: ingredient.name });\n }\n }\n }\n }\n\n /**\n * Adds a recipe to the shopping list, then automatically\n * recalculates the quantities and recategorize the ingredients.\n * @param recipe - The recipe to add.\n * @param options - Options for adding the recipe.\n * @throws Error if the recipe has alternatives without corresponding choices.\n */\n addRecipe(recipe: Recipe, options: AddedRecipeOptions = {}): void {\n // Validate that choices are provided for all alternatives\n const errorMessage = this.getUnresolvedAlternativesError(\n recipe,\n options.choices,\n );\n if (errorMessage) {\n throw new Error(errorMessage);\n }\n\n if (!options.scaling) {\n this.recipes.push({\n recipe,\n factor: options.scaling ?? 1,\n choices: options.choices,\n });\n } else {\n if (\"factor\" in options.scaling) {\n this.recipes.push({\n recipe,\n factor: options.scaling.factor,\n choices: options.choices,\n });\n } else {\n this.recipes.push({\n recipe,\n servings: options.scaling.servings,\n choices: options.choices,\n });\n }\n }\n this.calculateIngredients();\n this.categorize();\n }\n\n /**\n * Checks if a recipe has unresolved alternatives (alternatives without provided choices).\n * @param recipe - The recipe to check.\n * @param choices - The choices provided for the recipe.\n * @returns An error message if there are unresolved alternatives, undefined otherwise.\n */\n private getUnresolvedAlternativesError(\n recipe: Recipe,\n choices?: import(\"../types\").RecipeChoices,\n ): string | undefined {\n const missingItems: string[] = [];\n const missingGroups: string[] = [];\n\n // Check for inline alternatives without choices\n for (const itemId of recipe.choices.ingredientItems.keys()) {\n if (!choices?.ingredientItems?.has(itemId)) {\n missingItems.push(itemId);\n }\n }\n\n // Check for grouped alternatives without choices\n for (const groupId of recipe.choices.ingredientGroups.keys()) {\n if (!choices?.ingredientGroups?.has(groupId)) {\n missingGroups.push(groupId);\n }\n }\n\n if (missingItems.length === 0 && missingGroups.length === 0) {\n return undefined;\n }\n\n const parts: string[] = [];\n if (missingItems.length > 0) {\n parts.push(\n `ingredientItems: [${missingItems.map((i) => `'${i}'`).join(\", \")}]`,\n );\n }\n if (missingGroups.length > 0) {\n parts.push(\n `ingredientGroups: [${missingGroups.map((g) => `'${g}'`).join(\", \")}]`,\n );\n }\n return `Recipe has unresolved alternatives. Missing choices for: ${parts.join(\", \")}`;\n }\n\n /**\n * Removes a recipe from the shopping list, then automatically\n * recalculates the quantities and recategorize the ingredients.\n * @param index - The index of the recipe to remove.\n */\n removeRecipe(index: number) {\n if (index < 0 || index >= this.recipes.length) {\n throw new Error(\"Index out of bounds\");\n }\n this.recipes.splice(index, 1);\n this.calculateIngredients();\n this.categorize();\n }\n\n /**\n * Sets the category configuration for the shopping list\n * and automatically categorize current ingredients from the list.\n * @param config - The category configuration to parse.\n */\n setCategoryConfig(config: string | CategoryConfig) {\n if (typeof config === \"string\")\n this.categoryConfig = new CategoryConfig(config);\n else if (config instanceof CategoryConfig) this.categoryConfig = config;\n else throw new Error(\"Invalid category configuration\");\n this.categorize();\n }\n\n /**\n * Categorizes the ingredients in the shopping list\n * Will use the category config if any, otherwise all ingredients will be placed in the \"other\" category\n */\n categorize() {\n if (!this.categoryConfig) {\n this.categories = { other: this.ingredients };\n return;\n }\n\n const categories: CategorizedIngredients = { other: [] };\n for (const category of this.categoryConfig.categories) {\n categories[category.name] = [];\n }\n\n for (const ingredient of this.ingredients) {\n let found = false;\n for (const category of this.categoryConfig.categories) {\n for (const categoryIngredient of category.ingredients) {\n if (categoryIngredient.aliases.includes(ingredient.name)) {\n categories[category.name]!.push(ingredient);\n found = true;\n break;\n }\n }\n if (found) {\n break;\n }\n }\n if (!found) {\n categories.other!.push(ingredient);\n }\n }\n\n this.categories = categories;\n }\n}\n","import type {\n ProductOption,\n ProductSelection,\n AddedIngredient,\n CartContent,\n CartMatch,\n CartMisMatch,\n FixedNumericValue,\n Range,\n ProductOptionNormalized,\n ProductSizeNormalized,\n NoProductMatchErrorCode,\n FlatOrGroup,\n MaybeNestedGroup,\n QuantityWithUnitDef,\n} from \"../types\";\nimport { ProductCatalog } from \"./product_catalog\";\nimport { ShoppingList } from \"./shopping_list\";\nimport {\n NoProductCatalogForCartError,\n NoShoppingListForCartError,\n NoProductMatchError,\n} from \"../errors\";\nimport { resolveUnit } from \"../units/definitions\";\nimport { normalizeAllUnits } from \"../quantities/mutations\";\nimport { getNumericValue, multiplyQuantityValue } from \"../quantities/numeric\";\nimport { isAndGroup, isOrGroup } from \"../utils/type_guards\";\nimport { areUnitsGroupable } from \"../units/compatibility\";\nimport { solve, type Model } from \"yalps\";\n\n/**\n * Options for the {@link ShoppingCart} constructor\n * @category Types\n */\nexport interface ShoppingCartOptions {\n /**\n * A product catalog to connect to the cart\n */\n catalog?: ProductCatalog;\n /**\n * A shopping list to connect to the cart\n */\n list?: ShoppingList;\n}\n\n/**\n * Key information about the {@link ShoppingCart}\n * @category Types\n */\nexport interface ShoppingCartSummary {\n /**\n * The total price of the cart\n */\n totalPrice: number;\n /**\n * The total number of items in the cart\n */\n totalItems: number;\n}\n\n/**\n * Shopping Cart Manager: a tool to find the best combination of products to buy (defined in a {@link ProductCatalog}) to satisfy a {@link ShoppingList}.\n *\n * @example\n * ```ts\n * const shoppingList = new ShoppingList();\n * const recipe = new Recipe(\"@flour{600%g}\");\n * shoppingList.addRecipe(recipe);\n *\n * const catalog = new ProductCatalog();\n * catalog.products = [\n * {\n * id: \"flour-1kg\",\n * productName: \"Flour (1kg)\",\n * ingredientName: \"flour\",\n * price: 10,\n * size: { type: \"fixed\", value: { type: \"decimal\", value: 1000 } },\n * unit: \"g\",\n * },\n * {\n * id: \"flour-500g\",\n * productName: \"Flour (500g)\",\n * ingredientName: \"flour\",\n * price: 6,\n * size: { type: \"fixed\", value: { type: \"decimal\", value: 500 } },\n * unit: \"g\",\n * },\n * ];\n *\n * const shoppingCart = new ShoppingCart({list: shoppingList, catalog}))\n * shoppingCart.buildCart();\n * ```\n *\n * @category Classes\n */\nexport class ShoppingCart {\n /**\n * The product catalog to use for matching products\n */\n productCatalog?: ProductCatalog;\n /**\n * The shopping list to build the cart from\n */\n shoppingList?: ShoppingList;\n /**\n * The content of the cart\n */\n cart: CartContent = [];\n /**\n * The ingredients that were successfully matched with products\n */\n match: CartMatch = [];\n /**\n * The ingredients that could not be matched with products\n */\n misMatch: CartMisMatch = [];\n /**\n * Key information about the shopping cart\n */\n summary: ShoppingCartSummary;\n\n /**\n * Creates a new ShoppingCart instance\n * @param options - {@link ShoppingCartOptions | Options} for the constructor\n */\n constructor(options?: ShoppingCartOptions) {\n if (options?.catalog) this.productCatalog = options.catalog;\n if (options?.list) this.shoppingList = options.list;\n this.summary = { totalPrice: 0, totalItems: 0 };\n }\n\n /**\n * Sets the product catalog to use for matching products\n * To use if a catalog was not provided at the creation of the instance\n * @param catalog - The {@link ProductCatalog} to set\n */\n setProductCatalog(catalog: ProductCatalog) {\n this.productCatalog = catalog;\n }\n\n /**\n * Sets the shopping list to build the cart from.\n * To use if a shopping list was not provided at the creation of the instance\n * @param list - The {@link ShoppingList} to set\n */\n setShoppingList(list: ShoppingList) {\n this.shoppingList = list;\n }\n\n /**\n * Builds the cart from the shopping list and product catalog\n * @remarks\n * - If a combination of product(s) is successfully found for a given ingredient, the latter will be listed in the {@link ShoppingCart.match | match} array\n * in addition to that combination being added to the {@link ShoppingCart.cart | cart}.\n * - Otherwise, the latter will be listed in the {@link ShoppingCart.misMatch | misMatch} array. Possible causes can be:\n * - No product is listed in the catalog for that ingredient\n * - The ingredient has no quantity, a text quantity\n * - The ingredient's quantity unit is incompatible with the units of the candidate products listed in the catalog\n * @throws {@link NoProductCatalogForCartError} if no product catalog is set\n * @throws {@link NoShoppingListForCartError} if no shopping list is set\n * @returns `true` if all ingredients in the shopping list have been matched to products in the catalog, or `false` otherwise\n */\n buildCart(): boolean {\n this.resetCart();\n\n if (this.productCatalog === undefined) {\n throw new NoProductCatalogForCartError();\n } else if (this.shoppingList === undefined) {\n throw new NoShoppingListForCartError();\n }\n\n for (const ingredient of this.shoppingList.ingredients) {\n const productOptions = this.getProductOptions(ingredient);\n try {\n const optimumMatch = this.getOptimumMatch(ingredient, productOptions);\n this.cart.push(...optimumMatch);\n this.match.push({ ingredient, selection: optimumMatch });\n } catch (error) {\n /* v8 ignore else -- @preserve */\n if (error instanceof NoProductMatchError) {\n this.misMatch.push({ ingredient, reason: error.code });\n }\n }\n }\n\n this.summarize();\n\n return this.misMatch.length > 0;\n }\n\n /**\n * Gets the product options for a given ingredient\n * @param ingredient - The ingredient to get the product options for\n * @returns An array of {@link ProductOption}\n */\n private getProductOptions(ingredient: AddedIngredient): ProductOption[] {\n // this function is only called in buildCart() which starts by checking that a product catalog is present\n return this.productCatalog!.products.filter(\n (product) =>\n product.ingredientName === ingredient.name ||\n product.ingredientAliases?.includes(ingredient.name),\n );\n }\n\n /**\n * Gets the optimum match for a given ingredient and product option\n * @param ingredient - The ingredient to match\n * @param options - The product options to choose from\n * @returns An array of {@link ProductSelection}\n * @throws {@link NoProductMatchError} if no match can be found\n */\n private getOptimumMatch(\n ingredient: AddedIngredient,\n options: ProductOption[],\n ): ProductSelection[] {\n // If there's no product option, return an empty match\n if (options.length === 0)\n throw new NoProductMatchError(ingredient.name, \"noProduct\");\n // If the ingredient has no quantity, we can't match any product\n if (!ingredient.quantityTotal)\n throw new NoProductMatchError(ingredient.name, \"noQuantity\");\n\n // Normalize options units and scale size to base\n const normalizedOptions: ProductOptionNormalized[] = options.map(\n (option) => ({\n ...option,\n sizes: option.sizes.map((s): ProductSizeNormalized => {\n const resolvedUnit = resolveUnit(s.unit);\n return {\n size:\n resolvedUnit && \"toBase\" in resolvedUnit\n ? (multiplyQuantityValue(\n s.size,\n resolvedUnit.toBase,\n ) as FixedNumericValue)\n : s.size,\n unit: resolvedUnit,\n };\n }),\n }),\n );\n const normalizedQuantityTotal = normalizeAllUnits(ingredient.quantityTotal);\n\n function getOptimumMatchForQuantityParts(\n normalizedQuantities:\n | QuantityWithUnitDef\n | MaybeNestedGroup<QuantityWithUnitDef>,\n normalizedOptions: ProductOptionNormalized[],\n selection: ProductSelection[] = [],\n ): ProductSelection[] {\n if (isAndGroup(normalizedQuantities)) {\n for (const q of normalizedQuantities.and) {\n const result = getOptimumMatchForQuantityParts(\n q,\n normalizedOptions,\n selection,\n );\n selection.push(...result);\n }\n } else {\n const alternativeUnitsOfQuantity = isOrGroup(normalizedQuantities)\n ? (normalizedQuantities as FlatOrGroup<QuantityWithUnitDef>).or\n : [normalizedQuantities];\n const solutions: ProductSelection[][] = [];\n const errors = new Set<NoProductMatchErrorCode>();\n for (const alternative of alternativeUnitsOfQuantity) {\n // At this stage, we're treating individual Quantities we should try to match\n if (\n alternative.quantity.type === \"fixed\" &&\n alternative.quantity.value.type === \"text\"\n ) {\n errors.add(\"textValue\");\n continue;\n }\n // At this stage, we know there is a numerical quantity\n // So we scale it to base in order to calculate the correct quantity\n const scaledQuantity = multiplyQuantityValue(\n alternative.quantity,\n \"toBase\" in alternative.unit ? alternative.unit.toBase : 1,\n ) as FixedNumericValue | Range;\n alternative.quantity = scaledQuantity;\n // Are there compatible product options for that specific unit alternative?\n // A product is compatible if ANY of its sizes has a compatible unit\n const matchOptions = normalizedOptions.filter((option) =>\n option.sizes.some((s) =>\n areUnitsGroupable(alternative.unit, s.unit),\n ),\n );\n if (matchOptions.length > 0) {\n // Helper to find the compatible size for a product option\n const findCompatibleSize = (\n option: ProductOptionNormalized,\n ): ProductSizeNormalized =>\n option.sizes.find((s) =>\n areUnitsGroupable(alternative.unit, s.unit),\n )!;\n\n // Simple minimization exercise if only one product option\n if (matchOptions.length == 1) {\n const matchedOption = matchOptions[0]!;\n const compatibleSize = findCompatibleSize(matchedOption);\n const product = options.find(\n (opt) => opt.id === matchedOption.id,\n )!;\n // FixedValue\n const targetQuantity =\n scaledQuantity.type === \"fixed\"\n ? scaledQuantity.value\n : scaledQuantity.min;\n const resQuantity = Math.ceil(\n getNumericValue(targetQuantity) /\n getNumericValue(compatibleSize.size.value),\n );\n solutions.push([\n {\n product,\n quantity: resQuantity,\n totalPrice: resQuantity * matchedOption.price,\n },\n ]);\n continue;\n }\n\n // More complex problem if there are several options\n const model: Model = {\n direction: \"minimize\",\n objective: \"price\",\n integers: true,\n constraints: {\n size: {\n min:\n scaledQuantity.type === \"fixed\"\n ? getNumericValue(scaledQuantity.value)\n : getNumericValue(scaledQuantity.min),\n },\n },\n variables: matchOptions.reduce(\n (acc, option) => {\n const compatibleSize = findCompatibleSize(option);\n acc[option.id] = {\n price: option.price,\n size: getNumericValue(compatibleSize.size.value),\n };\n return acc;\n },\n {} as Record<string, { price: number; size: number }>,\n ),\n };\n\n const solution = solve(model);\n solutions.push(\n solution.variables.map((variable) => {\n const resProductSelection = {\n product: options.find((option) => option.id === variable[0])!,\n quantity: variable[1],\n };\n return {\n ...resProductSelection,\n totalPrice:\n resProductSelection.quantity *\n resProductSelection.product.price,\n };\n }),\n );\n } else {\n errors.add(\"incompatibleUnits\");\n }\n }\n // All alternatives were checked\n if (solutions.length === 0) {\n throw new NoProductMatchError(\n ingredient.name,\n errors.size === 1\n ? (errors.values().next().value as NoProductMatchErrorCode)\n : \"textValue_incompatibleUnits\",\n );\n } else {\n // We return the cheapest solution among those found\n return solutions.sort(\n (a, b) =>\n a.reduce((acc, item) => acc + item.totalPrice, 0) -\n b.reduce((acc, item) => acc + item.totalPrice, 0),\n )[0]!;\n }\n }\n return selection;\n }\n\n return getOptimumMatchForQuantityParts(\n normalizedQuantityTotal,\n normalizedOptions,\n );\n }\n\n /**\n * Reset the cart's properties\n */\n private resetCart() {\n this.cart = [];\n this.match = [];\n this.misMatch = [];\n this.summary = { totalPrice: 0, totalItems: 0 };\n }\n\n /**\n * Calculate the cart's key info and store it in the cart's {@link ShoppingCart.summary | summary} property.\n * This function is automatically invoked by {@link ShoppingCart.buildCart | buildCart() } method.\n * @returns the total price and number of items in the cart\n */\n summarize(): ShoppingCartSummary {\n this.summary.totalPrice = this.cart.reduce(\n (acc, item) => acc + item.totalPrice,\n 0,\n );\n this.summary.totalItems = this.cart.length;\n return this.summary;\n }\n}\n","import type {\n IngredientItem,\n RecipeChoices,\n FixedValue,\n Range,\n TextValue,\n DecimalValue,\n FractionValue,\n Unit,\n QuantityWithExtendedUnit,\n MaybeScalableQuantity,\n} from \"../types\";\nimport { Recipe } from \"../classes/recipe\";\n\n// ============================================================================\n// Quantity Formatting Helpers\n// ============================================================================\n\n/**\n * Map of common fractions to their Unicode vulgar fraction characters.\n */\nconst VULGAR_FRACTIONS: Record<string, string> = {\n \"1/2\": \"½\",\n \"1/3\": \"⅓\",\n \"2/3\": \"⅔\",\n \"1/4\": \"¼\",\n \"3/4\": \"¾\",\n \"1/8\": \"⅛\",\n \"3/8\": \"⅜\",\n \"5/8\": \"⅝\",\n \"7/8\": \"⅞\",\n};\n\n/**\n * Render a fraction using Unicode vulgar fraction characters when available.\n * Handles improper fractions by extracting the whole part (e.g., 5/4 → \"1¼\").\n *\n * @param num - The numerator\n * @param den - The denominator\n * @returns The fraction as a string, using vulgar characters if available\n * @category Helpers\n *\n * @example\n * ```typescript\n * renderFractionAsVulgar(1, 2); // \"½\"\n * renderFractionAsVulgar(3, 4); // \"¾\"\n * renderFractionAsVulgar(5, 4); // \"1¼\"\n * renderFractionAsVulgar(7, 3); // \"2⅓\"\n * renderFractionAsVulgar(2, 5); // \"2/5\" (no vulgar character available)\n * ```\n */\nexport function renderFractionAsVulgar(num: number, den: number): string {\n // Handle improper fractions (num >= den)\n const wholePart = Math.floor(num / den);\n const remainder = num % den;\n\n if (remainder === 0) {\n // Exact integer\n return String(wholePart);\n }\n\n const fractionKey = `${remainder}/${den}`;\n const vulgar = VULGAR_FRACTIONS[fractionKey];\n\n if (wholePart > 0) {\n // Mixed fraction: whole part + fractional part\n return vulgar\n ? `${wholePart}${vulgar}`\n : `${wholePart} ${remainder}/${den}`;\n }\n\n // Proper fraction only\n return vulgar ?? `${num}/${den}`;\n}\n\n/**\n * Format a numeric value (decimal or fraction) to a string.\n *\n * @param value - The decimal or fraction value to format\n * @param useVulgar - Whether to use Unicode vulgar fraction characters (default: false)\n * @returns The formatted string representation\n * @category Helpers\n *\n * @example\n * ```typescript\n * formatNumericValue({ type: \"decimal\", decimal: 1.5 }); // \"1.5\"\n * formatNumericValue({ type: \"fraction\", num: 1, den: 2 }); // \"1/2\"\n * formatNumericValue({ type: \"fraction\", num: 1, den: 2 }, true); // \"½\"\n * formatNumericValue({ type: \"fraction\", num: 5, den: 4 }, true); // \"1¼\"\n * ```\n */\nexport function formatNumericValue(\n value: DecimalValue | FractionValue,\n useVulgar: boolean = true,\n): string {\n if (value.type === \"decimal\") {\n return String(value.decimal);\n }\n if (useVulgar) {\n return renderFractionAsVulgar(value.num, value.den);\n }\n return `${value.num}/${value.den}`;\n}\n\n/**\n * Format a single value (text, decimal, or fraction) to a string.\n *\n * @param value - The value to format\n * @returns The formatted string representation\n * @category Helpers\n *\n * @example\n * ```typescript\n * formatSingleValue({ type: \"text\", text: \"a pinch\" }); // \"a pinch\"\n * formatSingleValue({ type: \"decimal\", decimal: 2 }); // \"2\"\n * formatSingleValue({ type: \"fraction\", num: 3, den: 4 }); // \"3/4\"\n * ```\n */\nexport function formatSingleValue(\n value: TextValue | DecimalValue | FractionValue,\n): string {\n if (value.type === \"text\") {\n return value.text;\n }\n return formatNumericValue(value);\n}\n\n/**\n * Format a quantity (fixed value or range) to a string.\n *\n * @param quantity - The quantity to format\n * @returns The formatted string representation\n * @category Helpers\n *\n * @example\n * ```typescript\n * formatQuantity({ type: \"fixed\", value: { type: \"decimal\", decimal: 100 } }); // \"100\"\n * formatQuantity({ type: \"range\", min: { type: \"decimal\", decimal: 1 }, max: { type: \"decimal\", decimal: 2 } }); // \"1-2\"\n * ```\n */\nexport function formatQuantity(quantity: FixedValue | Range): string {\n if (quantity.type === \"fixed\") {\n return formatSingleValue(quantity.value);\n }\n // Range\n const minStr = formatNumericValue(quantity.min);\n const maxStr = formatNumericValue(quantity.max);\n return `${minStr}-${maxStr}`;\n}\n\n/**\n * Format a unit to a string. Handles both plain string units and Unit objects.\n *\n * @param unit - The unit to format (string, Unit object, or undefined)\n * @returns The formatted unit string, or empty string if undefined\n * @category Helpers\n *\n * @example\n * ```typescript\n * formatUnit(\"g\"); // \"g\"\n * formatUnit({ name: \"grams\" }); // \"grams\"\n * formatUnit(undefined); // \"\"\n * ```\n */\nexport function formatUnit(unit: string | Unit | undefined): string {\n if (!unit) return \"\";\n if (typeof unit === \"string\") return unit;\n return unit.name;\n}\n\n/**\n * Format a quantity with its unit to a string.\n *\n * @param quantity - The quantity to format\n * @param unit - The unit to append (string, Unit object, or undefined)\n * @returns The formatted string with quantity and unit\n * @category Helpers\n *\n * @example\n * ```typescript\n * formatQuantityWithUnit({ type: \"fixed\", value: { type: \"decimal\", decimal: 100 } }, \"g\"); // \"100 g\"\n * formatQuantityWithUnit({ type: \"fixed\", value: { type: \"decimal\", decimal: 2 } }, undefined); // \"2\"\n * ```\n */\nexport function formatQuantityWithUnit(\n quantity: FixedValue | Range | undefined,\n unit: string | Unit | undefined,\n): string {\n if (!quantity) return \"\";\n const qty = formatQuantity(quantity);\n const unitStr = formatUnit(unit);\n return unitStr ? `${qty} ${unitStr}` : qty;\n}\n\n/**\n * Format a QuantityWithExtendedUnit to a string.\n *\n * @param item - The quantity with extended unit to format\n * @returns The formatted string\n * @category Helpers\n */\nexport function formatExtendedQuantity(item: QuantityWithExtendedUnit): string {\n return formatQuantityWithUnit(item.quantity, item.unit);\n}\n\n/**\n * Format an IngredientItemQuantity with all its equivalents to a string.\n *\n * @param itemQuantity - The ingredient item quantity to format\n * @param separator - The separator between primary and equivalent quantities (default: \" | \")\n * @returns The formatted string with all quantities\n * @category Helpers\n *\n * @example\n * ```typescript\n * // For an ingredient like @flour{100%g|3.5%oz}\n * formatItemQuantity(itemQuantity); // \"100 g | 3.5 oz\"\n * formatItemQuantity(itemQuantity, \" / \"); // \"100 g / 3.5 oz\"\n * ```\n */\nexport function formatItemQuantity(\n itemQuantity: MaybeScalableQuantity,\n separator: string = \" | \",\n): string {\n const parts: string[] = [];\n\n // Primary quantity\n parts.push(formatExtendedQuantity(itemQuantity));\n\n // Equivalents\n if (itemQuantity.equivalents) {\n for (const eq of itemQuantity.equivalents) {\n parts.push(formatExtendedQuantity(eq));\n }\n }\n\n return parts.join(separator);\n}\n\n// ============================================================================\n// Ingredient Item Helpers\n// ============================================================================\n\n/**\n * Check if an ingredient item is a grouped alternative (vs inline alternative).\n *\n * Grouped alternatives are ingredients that share a group key (e.g., `@|milk|...`)\n * and are distributed across multiple tokens in the recipe.\n *\n * @param item - The ingredient item to check\n * @returns true if this is a grouped alternative\n * @category Helpers\n *\n * @example\n * ```typescript\n * for (const item of step.items) {\n * if (item.type === 'ingredient') {\n * if (isGroupedItem(item)) {\n * // Handle grouped alternative (e.g., show with strikethrough if not selected)\n * } else {\n * // Handle inline alternative (e.g., hide if not selected)\n * }\n * }\n * }\n * ```\n */\nexport function isGroupedItem(item: IngredientItem): boolean {\n return item.group !== undefined;\n}\n\n// ============================================================================\n// Alternative Selection Helpers\n// ============================================================================\n\n/**\n * Determines if a specific alternative in an IngredientItem is selected\n * based on the applied choices.\n *\n * Use this in renderers to determine how an ingredient alternative should be displayed.\n *\n * @param recipe - The Recipe instance containing choices\n * @param choices - The choices that have been made\n * @param item - The IngredientItem to check\n * @param alternativeIndex - The index within item.alternatives to check (for inline alternatives only)\n * @returns true if this alternative is the selected one\n * @category Helpers\n *\n * @example\n * ```typescript\n * const recipe = new Recipe(cooklangText);\n * for (const item of step.items) {\n * if (item.type === 'ingredient') {\n * item.alternatives.forEach((alt, idx) => {\n * const isSelected = isAlternativeSelected(item, idx, recipe, choices);\n * // Render differently based on isSelected\n * });\n * }\n * }\n * ```\n */\nexport function isAlternativeSelected(\n recipe: Recipe,\n choices: RecipeChoices,\n item: IngredientItem,\n alternativeIndex?: number,\n): boolean {\n // Grouped alternatives: check ingredientGroups map\n if (item.group) {\n // Get the selected index in the group\n const selectedIndex = choices?.ingredientGroups?.get(item.group);\n // Get the alternatives array for this group\n const groupAlternatives = recipe.choices.ingredientGroups.get(item.group);\n if (\n groupAlternatives &&\n selectedIndex !== undefined &&\n selectedIndex < groupAlternatives.length\n ) {\n // Check if the selected alternative's itemId matches this item's id\n const selectedItemId = groupAlternatives[selectedIndex]?.itemId;\n return selectedItemId === item.id;\n }\n return false;\n }\n\n // Inline alternatives: check ingredientItems map\n const selectedIndex = choices?.ingredientItems?.get(item.id);\n return alternativeIndex === selectedIndex;\n}\n"],"mappings":"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","names":["escapeCache","Map","Flags","GLOBAL","NON_SENSITIVE","MULTILINE","DOT_ALL","UNICODE","STICKY","Ranges","Object","freeze","digit","lowercaseLetter","uppercaseLetter","letter","alphanumeric","anyCharacter","Quantifiers","zeroOrMore","oneOrMore","optional","HumanRegex","constructor","this","parts","flags","Set","add","special","word","whitespace","nonWhitespace","text","has","set","replace","get","or","name","range","Error","chars","lazy","lastPart","pop","newline","pattern","hasSpecialCharacter","hasDigit","hasLetter","n","min","max","startGroup","startCaptureGroup","wordBoundary","nonWordBoundary","endGroup","startAnchor","endAnchor","global","nonSensitive","multiline","dotAll","sticky","variant","validVariants","unicodeDigit","unicodePunctuation","unicodeSymbol","count","length","push","ipv4Octet","protocol","www","tld","path","part","toString","join","toRegExp","RegExp","createRegex","Patterns","createCachedPattern","builder","regex","source","email","literal","atLeast","url","phoneInternational","between","r","import_big","a","Big","a","Big","e","i","e","a","TOML","import_big","units","a","unitRatio","Big","import_big","ing","i","item","Big","factor","i","normalizedOptions","a","selectedIndex"]}
|