{"version":3,"file":"parse-ingredient.production.mjs","names":[],"sources":["../src/constants.ts","../src/unitLookup.ts","../src/convertUnit.ts","../src/parseIngredient.ts"],"sourcesContent":["import { numericRegex } from 'numeric-quantity';\nimport { ParseIngredientOptions, UnitOfMeasureDefinitions } from './types';\n\n// --- i18n Utilities ---\n\n/**\n * Escapes special regex characters in a string.\n */\nexport const escapeRegex = (str: string): string => str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/**\n * Builds a regex that matches any of the given patterns at the start of a string,\n * followed by whitespace. Strings are escaped and treated as literal prefixes.\n * RegExp patterns have their source extracted and combined.\n */\nexport const buildPrefixPatternRegex = (patterns: (string | RegExp)[]): RegExp | null => {\n  if (patterns.length === 0) return null;\n  const parts = patterns.map(p =>\n    p instanceof RegExp ? `(?:${p.source})` : `(?:${escapeRegex(p)})\\\\s`\n  );\n  return new RegExp(`^(?:${parts.join('|')})`, 'iu');\n};\n\n/**\n * Builds a regex source string for range separators (dashes and word separators).\n * Always includes dash characters (-, –, —), plus any custom word separators.\n */\nexport const buildRangeSeparatorSource = (words: (string | RegExp)[]): string => {\n  const wordParts = words.map(w =>\n    w instanceof RegExp ? `(?:${w.source})` : `(?:${escapeRegex(w)})`\n  );\n  // Always include dashes, then word separators followed by whitespace\n  return `(-|–|—|(?:${wordParts.join('|')})\\\\s)`;\n};\n\n/**\n * Builds a regex that matches range separators at the start of a string.\n */\nexport const buildRangeSeparatorRegex = (words: (string | RegExp)[]): RegExp =>\n  new RegExp(`^${buildRangeSeparatorSource(words)}`, 'iu');\n\n/**\n * Builds a regex that matches any of the given words or patterns at the start of a string.\n * Strings are matched as whole words followed by whitespace.\n * RegExp patterns are used as-is for more complex matching (e.g., French elisions).\n */\nexport const buildStripPrefixRegex = (patterns: (string | RegExp)[]): RegExp | null => {\n  if (patterns.length === 0) return null;\n  const parts = patterns.map(p =>\n    p instanceof RegExp ? `(?:${p.source})` : `(?:${escapeRegex(p)})\\\\s+`\n  );\n  return new RegExp(`^(?:${parts.join('|')})`, 'iu');\n};\n\n/**\n * Builds a regex that matches approximation/modifier prefixes at the start of\n * quantity expressions (e.g., \"about\", \"ca.\", \"bis zu\"), followed by optional whitespace.\n */\nexport const buildLeadingQuantityPrefixRegex = (patterns: (string | RegExp)[]): RegExp | null => {\n  if (patterns.length === 0) return null;\n  // Uses `\\s*` (optional whitespace) instead of `\\s+` (required whitespace, as in\n  // buildStripPrefixRegex) because quantity prefixes like \"ca.\" may appear directly\n  // adjacent to the number (e.g., \"ca.200g\").\n  const parts = patterns.map(p =>\n    p instanceof RegExp ? `(?:${p.source})\\\\s*` : `(?:${escapeRegex(p)})\\\\s*`\n  );\n  return new RegExp(`^(?:${parts.join('|')})`, 'iu');\n};\n\n/**\n * Builds a regex that matches any of the given words at the end of a string,\n * preceded by whitespace. Used for trailing quantity context like \"from\" or \"of\".\n */\nexport const buildTrailingContextRegex = (words: string[]): RegExp =>\n  new RegExp(`\\\\s+(?:${words.map(escapeRegex).join('|')})$`, 'iu');\n\n// --- Default i18n Values ---\n\n/**\n * Default group header prefixes (e.g., \"For the icing:\").\n */\nexport const defaultGroupHeaderPatterns = ['For'] as const;\n\n/**\n * Default range separator words (e.g., \"1 to 2\", \"1 or 2\").\n */\nexport const defaultRangeSeparators = ['or', 'to'] as const;\n\n/**\n * Default words to strip from the beginning of descriptions.\n */\nexport const defaultDescriptionStripPrefixes = ['of'] as const;\n\n/**\n * Default words that indicate trailing quantity context.\n */\nexport const defaultTrailingQuantityContext = ['from', 'of'] as const;\n/**\n * Default words/patterns that are stripped from the beginning of quantity expressions.\n */\nexport const defaultLeadingQuantityPrefixes = [] as const;\n\n/**\n * Default options for {@link parseIngredient}.\n */\nexport const defaultOptions: Required<ParseIngredientOptions> = {\n  additionalUOMs: {},\n  allowLeadingOf: false,\n  normalizeUOM: false,\n  ignoreUOMs: [],\n  decimalSeparator: '.',\n  groupHeaderPatterns: defaultGroupHeaderPatterns as unknown as string[],\n  rangeSeparators: defaultRangeSeparators as unknown as string[],\n  descriptionStripPrefixes: defaultDescriptionStripPrefixes as unknown as (string | RegExp)[],\n  trailingQuantityContext: defaultTrailingQuantityContext as unknown as string[],\n  leadingQuantityPrefixes: defaultLeadingQuantityPrefixes as unknown as (string | RegExp)[],\n  includeMeta: false,\n  partialUnitMatching: false,\n} as const;\n\n// --- Legacy Exports (for backward compatibility) ---\n\n/**\n * List of \"for\" equivalents.\n * @deprecated Use `defaultGroupHeaderPatterns` instead.\n */\nexport const fors: typeof defaultGroupHeaderPatterns = defaultGroupHeaderPatterns;\n\n/**\n * Regex to capture \"for\" equivalents.\n * @deprecated Build dynamically using `buildPrefixPatternRegex(options.groupHeaderPatterns)`.\n */\nexport const forsRegEx: RegExp = buildPrefixPatternRegex(\n  defaultGroupHeaderPatterns as unknown as string[]\n)!;\n\n/**\n * List of range separators.\n * @deprecated Use `defaultRangeSeparators` instead.\n */\nexport const rangeSeparatorWords: typeof defaultRangeSeparators = defaultRangeSeparators;\n\n/**\n * Regex to capture range separators.\n * @deprecated Build dynamically using `buildRangeSeparatorRegex(options.rangeSeparators)`.\n */\nexport const rangeSeparatorRegEx: RegExp = buildRangeSeparatorRegex(\n  defaultRangeSeparators as unknown as string[]\n);\n\n/**\n * Regex to capture the first word of a description to check if it's a unit of measure.\n */\nexport const firstWordRegEx: RegExp =\n  /^(fl(?:uid)?(?:\\s+|-)(?:oz|ounces?)|[\\p{L}\\p{N}_]+(?:[./-][\\p{L}\\p{N}_]+|\\([\\p{L}\\p{N}_]+\\))*[-.]?)(.+)?/iu;\n\nconst numericRegexAnywhere = numericRegex.source.replace('^', '').replace(/\\$$/, '');\n\n/**\n * Builds a regex to capture trailing quantity and unit of measure,\n * using the provided range separator words.\n */\nexport const buildTrailingQuantityRegex = (rangeSeparators: (string | RegExp)[]): RegExp => {\n  const rangeSeparatorSource = buildRangeSeparatorSource(rangeSeparators);\n  return new RegExp(\n    `(,|:|-|–|—|x|⨯)?\\\\s*((${numericRegexAnywhere})\\\\s*(${rangeSeparatorSource}))?\\\\s*(${numericRegexAnywhere})\\\\s*(fl(?:uid)?(?:\\\\s+|-)(?:oz|ounces?)|[\\\\p{L}\\\\p{N}_]+(?:[./-][\\\\p{L}\\\\p{N}_]+|\\\\([\\\\p{L}\\\\p{N}_]+\\\\))*[-.]?)?$`,\n    'iu'\n  );\n};\n\n/**\n * Regex to capture trailing quantity and unit of measure.\n * @deprecated Build dynamically using `buildTrailingQuantityRegex(options.rangeSeparators)`.\n */\nexport const trailingQuantityRegEx: RegExp = buildTrailingQuantityRegex(\n  defaultRangeSeparators as unknown as string[]\n);\n\n/**\n * List of \"of\" equivalents.\n * @deprecated Use `defaultDescriptionStripPrefixes` instead.\n */\nexport const ofs: typeof defaultDescriptionStripPrefixes = defaultDescriptionStripPrefixes;\n\n/**\n * Regex to capture \"of\" equivalents at the beginning of a string.\n * @deprecated Build dynamically using `buildStripPrefixRegex(options.descriptionStripPrefixes)`.\n */\nexport const ofRegEx: RegExp = buildStripPrefixRegex(\n  defaultDescriptionStripPrefixes as unknown as string[]\n)!;\n\n/**\n * List of \"from\" equivalents.\n * @deprecated Use `defaultTrailingQuantityContext` instead.\n */\nexport const froms: typeof defaultTrailingQuantityContext = defaultTrailingQuantityContext;\n\n/**\n * Regex to capture \"from\" equivalents at the end of a string.\n * @deprecated Build dynamically using `buildTrailingContextRegex(options.trailingQuantityContext)`.\n */\nexport const fromRegEx: RegExp = buildTrailingContextRegex(\n  defaultTrailingQuantityContext as unknown as string[]\n);\n\n/**\n * Default unit of measure specifications.\n */\nexport const unitsOfMeasure: UnitOfMeasureDefinitions = {\n  // Count units (no conversion factor)\n  bag: {\n    short: 'bag',\n    plural: 'bags',\n    alternates: [] satisfies string[],\n    type: 'count',\n  },\n  box: {\n    short: 'box',\n    plural: 'boxes',\n    alternates: [] satisfies string[],\n    type: 'count',\n  },\n  bunch: {\n    short: 'bunch',\n    plural: 'bunches',\n    alternates: [] satisfies string[],\n    type: 'count',\n  },\n  can: {\n    short: 'can',\n    plural: 'cans',\n    alternates: [] satisfies string[],\n    type: 'count',\n  },\n  carton: {\n    short: 'carton',\n    plural: 'cartons',\n    alternates: [] satisfies string[],\n    type: 'count',\n  },\n  clove: {\n    short: 'clove',\n    plural: 'cloves',\n    alternates: [] satisfies string[],\n    type: 'count',\n  },\n  container: {\n    short: 'container',\n    plural: 'containers',\n    alternates: [] satisfies string[],\n    type: 'count',\n  },\n  dozen: {\n    short: 'dz',\n    plural: 'dozen',\n    alternates: ['dz.'] satisfies string[],\n    type: 'count',\n  },\n  ear: {\n    short: 'ear',\n    plural: 'ears',\n    alternates: [] satisfies string[],\n    type: 'count',\n  },\n  head: {\n    short: 'head',\n    plural: 'heads',\n    alternates: [] satisfies string[],\n    type: 'count',\n  },\n  pack: {\n    short: 'pack',\n    plural: 'packs',\n    alternates: [] satisfies string[],\n    type: 'count',\n  },\n  package: {\n    short: 'pkg',\n    plural: 'packages',\n    alternates: ['pkg.', 'pkgs', 'pkgs.'] satisfies string[],\n    type: 'count',\n  },\n  piece: {\n    short: 'piece',\n    plural: 'pieces',\n    alternates: ['pc', 'pc.', 'pcs', 'pcs.'] satisfies string[],\n    type: 'count',\n  },\n  sprig: {\n    short: 'sprig',\n    plural: 'sprigs',\n    alternates: [] satisfies string[],\n    type: 'count',\n  },\n  stick: {\n    short: 'stick',\n    plural: 'sticks',\n    alternates: [] satisfies string[],\n    type: 'count',\n  },\n\n  // Other units (no conversion factor)\n  large: {\n    short: 'lg',\n    plural: 'large',\n    alternates: ['lg', 'lg.'] satisfies string[],\n    type: 'other',\n  },\n  medium: {\n    short: 'md',\n    plural: 'medium',\n    alternates: ['med', 'med.', 'md.'] satisfies string[],\n    type: 'other',\n  },\n  small: {\n    short: 'sm',\n    plural: 'small',\n    alternates: ['sm.'] satisfies string[],\n    type: 'other',\n  },\n\n  // Length units (conversion factor in mm)\n  centimeter: {\n    short: 'cm',\n    plural: 'centimeters',\n    alternates: ['cm.'] satisfies string[],\n    type: 'length',\n    conversionFactor: 10,\n  },\n  foot: {\n    short: 'ft',\n    plural: 'feet',\n    alternates: ['ft.'] satisfies string[],\n    type: 'length',\n    conversionFactor: 304.8,\n  },\n  inch: {\n    short: 'in',\n    plural: 'inches',\n    alternates: ['in.'] satisfies string[],\n    type: 'length',\n    conversionFactor: 25.4,\n  },\n  meter: {\n    short: 'm',\n    plural: 'meters',\n    alternates: ['m.'] satisfies string[],\n    type: 'length',\n    conversionFactor: 1000,\n  },\n  millimeter: {\n    short: 'mm',\n    plural: 'millimeters',\n    alternates: ['mm.'] satisfies string[],\n    type: 'length',\n    conversionFactor: 1,\n  },\n  yard: {\n    short: 'yd',\n    plural: 'yards',\n    alternates: ['yd.', 'yds.'] satisfies string[],\n    type: 'length',\n    conversionFactor: 914.4,\n  },\n\n  // Mass units (conversion factor in g)\n  gram: {\n    short: 'g',\n    plural: 'grams',\n    alternates: ['g.'] satisfies string[],\n    type: 'mass',\n    conversionFactor: 1,\n  },\n  kilogram: {\n    short: 'kg',\n    plural: 'kilograms',\n    alternates: ['kg.'] satisfies string[],\n    type: 'mass',\n    conversionFactor: 1000,\n  },\n  milligram: {\n    short: 'mg',\n    plural: 'milligrams',\n    alternates: ['mg.'] satisfies string[],\n    type: 'mass',\n    conversionFactor: 0.001,\n  },\n  ounce: {\n    short: 'oz',\n    plural: 'ounces',\n    alternates: ['oz.'] satisfies string[],\n    type: 'mass',\n    conversionFactor: 28.349523,\n  },\n  pound: {\n    short: 'lb',\n    plural: 'pounds',\n    alternates: ['lb.', 'lbs', 'lbs.'] satisfies string[],\n    type: 'mass',\n    conversionFactor: 453.59237,\n  },\n\n  // Volume units (conversion factor in ml)\n  cup: {\n    short: 'c',\n    plural: 'cups',\n    alternates: ['c.', 'C'] satisfies string[],\n    type: 'volume',\n    conversionFactor: { us: 236.58824, imperial: 284.13063, metric: 250 },\n  },\n  deciliter: {\n    short: 'dl',\n    plural: 'deciliters',\n    alternates: ['dl.'] satisfies string[],\n    type: 'volume',\n    conversionFactor: 100,\n  },\n  'fluid ounce': {\n    short: 'fl oz',\n    plural: 'fluid ounces',\n    alternates: [\n      'fluidounce',\n      'floz',\n      'fl-oz',\n      'fluid-ounce',\n      'fluid-ounces',\n      'fluidounces',\n      'fl ounce',\n      'fl ounces',\n      'fl-ounce',\n      'fl-ounces',\n      'fluid oz',\n      'fluid-oz',\n    ] satisfies string[],\n    type: 'volume',\n    conversionFactor: { us: 29.57353, imperial: 28.413063 },\n  },\n  gallon: {\n    short: 'gal',\n    plural: 'gallons',\n    alternates: ['gal.'] satisfies string[],\n    type: 'volume',\n    conversionFactor: { us: 3785.4118, imperial: 4546.09 },\n  },\n  liter: {\n    short: 'l',\n    plural: 'liters',\n    alternates: ['l.'] satisfies string[],\n    type: 'volume',\n    conversionFactor: 1000,\n  },\n  milliliter: {\n    short: 'ml',\n    plural: 'milliliters',\n    alternates: ['mL', 'ml.', 'mL.'] satisfies string[],\n    type: 'volume',\n    conversionFactor: 1,\n  },\n  pint: {\n    short: 'pt',\n    plural: 'pints',\n    alternates: ['pt.'] satisfies string[],\n    type: 'volume',\n    conversionFactor: { us: 473.17647, imperial: 568.26125 },\n  },\n  quart: {\n    short: 'qt',\n    plural: 'quarts',\n    alternates: ['qt.', 'qts', 'qts.'] satisfies string[],\n    type: 'volume',\n    conversionFactor: { us: 946.35295, imperial: 1136.5225 },\n  },\n  tablespoon: {\n    short: 'tbsp',\n    plural: 'tablespoons',\n    alternates: ['tbsp.', 'T', 'Tbsp.', 'Tbsp', 'tablespoonful'] satisfies string[],\n    type: 'volume',\n    conversionFactor: { us: 14.786765, imperial: 15, metric: 15 },\n  },\n  teaspoon: {\n    short: 'tsp',\n    plural: 'teaspoons',\n    alternates: ['tsp.', 't', 'teaspoonful'] satisfies string[],\n    type: 'volume',\n    conversionFactor: { us: 4.9289216, imperial: 5, metric: 5 },\n  },\n\n  // Volume units without conversion factor (imprecise)\n  dash: {\n    short: 'dash',\n    plural: 'dashes',\n    alternates: [] satisfies string[],\n    type: 'volume',\n  },\n  drop: {\n    short: 'drop',\n    plural: 'drops',\n    alternates: [] satisfies string[],\n    type: 'volume',\n  },\n  pinch: {\n    short: 'pinch',\n    plural: 'pinches',\n    alternates: [] satisfies string[],\n    type: 'volume',\n  },\n} as const;\n","import { unitsOfMeasure } from './constants';\nimport { UnitOfMeasureDefinitions } from './types';\n\n/**\n * Result of building unit lookup maps.\n */\nexport interface UnitLookupMaps {\n  /** Case-sensitive map (exact matches only) */\n  caseSensitive: Map<string, string>;\n  /** Case-insensitive map (lowercase keys) */\n  caseInsensitive: Map<string, string>;\n}\n\n/**\n * Builds Maps for unit lookup. Returns both case-sensitive and case-insensitive maps.\n * The case-sensitive map should be checked first to handle cases like 'T' (tablespoon)\n * vs 't' (teaspoon).\n */\nexport const buildUnitLookupMaps = (\n  additionalUOMs: UnitOfMeasureDefinitions = {}\n): UnitLookupMaps => {\n  const caseSensitive = new Map<string, string>();\n  const caseInsensitive = new Map<string, string>();\n\n  // Helper to add versions to maps (first one wins for case-insensitive)\n  const addToMaps = (id: string, def: UnitOfMeasureDefinitions[string]) => {\n    const versions = [id, def.short, def.plural, ...def.alternates];\n    for (const version of versions) {\n      // For case-sensitive, later entries override (so additionalUOMs wins)\n      caseSensitive.set(version, id);\n      // For case-insensitive, later entries also override\n      caseInsensitive.set(version.toLowerCase(), id);\n    }\n  };\n\n  // Process default UOMs first\n  for (const [id, def] of Object.entries(unitsOfMeasure)) {\n    addToMaps(id, def);\n  }\n\n  // Process additionalUOMs second so they override defaults\n  for (const [id, def] of Object.entries(additionalUOMs)) {\n    addToMaps(id, def);\n  }\n\n  return { caseSensitive, caseInsensitive };\n};\n\n/**\n * Looks up a unit ID from the maps, trying case-sensitive first.\n */\nexport const lookupUnit = (unit: string, maps: UnitLookupMaps): string | null =>\n  maps.caseSensitive.get(unit) ?? maps.caseInsensitive.get(unit.toLowerCase()) ?? null;\n\n/**\n * Cached lookup maps for the default unitsOfMeasure (no additionalUOMs).\n * Lazily initialized on first use.\n */\nlet defaultLookupMaps: UnitLookupMaps | null = null;\n\n/**\n * Gets the default lookup maps, creating them if needed.\n */\nexport const getDefaultUnitLookupMaps = (): UnitLookupMaps =>\n  defaultLookupMaps ?? (defaultLookupMaps = buildUnitLookupMaps());\n\n/**\n * Collects all known UOM strings from the lookup maps, sorted longest-first.\n */\nexport const collectUOMStrings = (maps: UnitLookupMaps): string[] => {\n  const keys = [...maps.caseSensitive.keys()];\n  keys.sort((a, b) => b.length - a.length);\n  return keys;\n};\n","import { unitsOfMeasure } from './constants';\nimport {\n  MultiSystemConversionFactor,\n  ParseIngredientOptions,\n  UnitOfMeasureDefinitions,\n  UnitSystem,\n} from './types';\nimport { buildUnitLookupMaps, getDefaultUnitLookupMaps, lookupUnit } from './unitLookup';\n\nexport type IdentifyUnitOptions = Pick<ParseIngredientOptions, 'additionalUOMs' | 'ignoreUOMs'>;\n\n/**\n * Identifies a unit of measure from a string, returning the canonical unit ID.\n * Matches against the unit ID, short form, plural form, and all alternates.\n * Case-sensitive matches are tried first (e.g., 'T' = tablespoon, 't' = teaspoon),\n * then falls back to case-insensitive matching.\n *\n * @returns The canonical unit ID (e.g., 'cup'), or `null` if the unit is not recognized\n *          or is in the `ignoreUOMs` list.\n *\n * @example\n * ```ts\n * identifyUnit('cups') // 'cup'\n * identifyUnit('c') // 'cup'\n * identifyUnit('T') // 'tablespoon'\n * identifyUnit('t') // 'teaspoon'\n * identifyUnit('tbsp') // 'tablespoon'\n * identifyUnit('unknown') // null\n * identifyUnit('large', { ignoreUOMs: ['large'] }) // null\n * ```\n */\nexport const identifyUnit = (\n  /** The unit string to identify (e.g., 'cups', 'c', 'C', 'cup'). */\n  unit: string,\n  /** Options for unit identification. */\n  options: IdentifyUnitOptions = {}\n): string | null => {\n  const { additionalUOMs = {}, ignoreUOMs = [] } = options;\n\n  // Check if the unit should be ignored (case-insensitive)\n  if (ignoreUOMs.length > 0) {\n    const unitLC = unit.toLowerCase();\n    if (ignoreUOMs.some(ignored => ignored.toLowerCase() === unitLC)) {\n      return null;\n    }\n  }\n\n  const hasAdditionalUOMs = Object.keys(additionalUOMs).length > 0;\n  const maps = hasAdditionalUOMs ? buildUnitLookupMaps(additionalUOMs) : getDefaultUnitLookupMaps();\n\n  return lookupUnit(unit, maps);\n};\n\n/**\n * Options for {@link convertUnit}.\n */\nexport interface ConvertUnitOptions {\n  /**\n   * The measurement system to use when units have different US/Imperial conversion factors.\n   *\n   * @default 'us'\n   */\n  fromSystem?: UnitSystem;\n  /**\n   * The measurement system to use when units have different US/Imperial conversion factors.\n   *\n   * @default 'us'\n   */\n  toSystem?: UnitSystem;\n  /**\n   * Additional unit definitions to use for conversion.\n   * These are merged with the default {@link unitsOfMeasure}.\n   */\n  additionalUOMs?: UnitOfMeasureDefinitions;\n}\n\n/**\n * Gets the conversion factor for a unit, handling both single and multi-system factors.\n */\nconst getConversionFactor = (\n  factor: number | MultiSystemConversionFactor | undefined,\n  system: UnitSystem\n): number | null =>\n  factor === undefined ? null : typeof factor === 'number' ? factor : (factor[system] ?? null);\n\n/**\n * Converts a value from one unit to another.\n *\n * @returns The converted value, or `null` if conversion is not possible\n *          (incompatible types, missing conversion factors, or unknown units).\n *\n * @example\n * ```ts\n * convertUnit(1, 'cup', 'milliliter') // ~236.588 (US)\n * convertUnit(1, 'cup', 'milliliter', { fromSystem: 'imperial' }) // ~284.131\n * convertUnit(1, 'pound', 'gram') // ~453.592\n * convertUnit(1, 'cup', 'gram') // null (incompatible types)\n * ```\n */\nexport const convertUnit = (\n  /** The numeric value to convert. */\n  value: number,\n  /** The unit to convert from (unit ID, short, plural, or alternate spelling). */\n  fromUnit: string,\n  /** The unit to convert to (unit ID, short, plural, or alternate spelling). */\n  toUnit: string,\n  /** Conversion options. */\n  options: ConvertUnitOptions = {}\n): number | null => {\n  const { fromSystem = 'us', toSystem = 'us', additionalUOMs = {} } = options;\n  // Normalize system names to lowercase for case-insensitive matching\n  const normalizedFromSystem = fromSystem.toLowerCase() as UnitSystem;\n  const normalizedToSystem = toSystem.toLowerCase() as UnitSystem;\n  const mergedUOMs = { ...unitsOfMeasure, ...additionalUOMs };\n\n  const fromUnitID = identifyUnit(fromUnit, { additionalUOMs });\n  const toUnitID = identifyUnit(toUnit, { additionalUOMs });\n\n  // Unknown units\n  if (!fromUnitID || !toUnitID) {\n    return null;\n  }\n\n  // Same unit with same system - return value as-is\n  if (fromUnitID === toUnitID && normalizedFromSystem === normalizedToSystem) {\n    return value;\n  }\n\n  const fromDef = mergedUOMs[fromUnitID];\n  const toDef = mergedUOMs[toUnitID];\n\n  // Incompatible or missing types\n  if (!fromDef.type || !toDef.type || fromDef.type !== toDef.type) {\n    return null;\n  }\n\n  const fromFactor = getConversionFactor(fromDef.conversionFactor, normalizedFromSystem);\n  const toFactor = getConversionFactor(toDef.conversionFactor, normalizedToSystem);\n\n  // Missing conversion factors\n  if (fromFactor === null || toFactor === null) {\n    return null;\n  }\n\n  // Convert via base unit: value * fromFactor / toFactor\n  const result = (value * fromFactor) / toFactor;\n  return Math.round(result * 1e6) / 1e6;\n};\n","import { numericQuantity, NumericQuantityOptions } from 'numeric-quantity';\nimport {\n  buildLeadingQuantityPrefixRegex,\n  buildPrefixPatternRegex,\n  buildRangeSeparatorRegex,\n  buildStripPrefixRegex,\n  buildTrailingContextRegex,\n  buildTrailingQuantityRegex,\n  defaultOptions,\n  firstWordRegEx,\n} from './constants';\nimport { identifyUnit } from './convertUnit';\nimport type { Ingredient, ParseIngredientOptions } from './types';\nimport { buildUnitLookupMaps, collectUOMStrings, getDefaultUnitLookupMaps } from './unitLookup';\n\nconst newLineRegExp = /\\r?\\n/;\nconst nextWordRegExp = /^([\\p{L}\\p{N}_]+(?:[.-]?[\\p{L}\\p{N}_]+)*[-.]?)(?:\\s+|$)/iu;\n\n/**\n * Repeatedly strips configured quantity prefixes from the start of a string.\n */\nconst stripLeadingQuantityPrefixes = (text: string, prefixRegex: RegExp | null): string => {\n  if (!text || !prefixRegex) return text;\n  let out = text.trimStart();\n  while (out) {\n    const match = prefixRegex.exec(out);\n    // Break if no match or if the match is zero-length to prevent infinite loops\n    // (empty string is falsy, so `!match[0]` catches both null/undefined and \"\")\n    if (!match || !match[0]) break;\n    out = out.slice(match[0].length).trimStart();\n  }\n  return out;\n};\n\n/**\n * Parses a string or array of strings into an array of recipe ingredient objects\n */\nexport const parseIngredient = (\n  /**\n   * The ingredient list, as plain text or an array of strings.\n   */\n  ingredientText: string | string[],\n  /**\n   * Configuration options. Defaults to {@link defaultOptions}.\n   */\n  options: ParseIngredientOptions = defaultOptions\n): Ingredient[] => {\n  const opts = { ...defaultOptions, ...options };\n  const nqOpts: NumericQuantityOptions | undefined =\n    opts.decimalSeparator === ',' ? { decimalSeparator: ',' } : undefined;\n\n  // Pre-compute lowercase ignored UOMs for the trailing quantity bail-out check\n  const ignoredUOMsLC = opts.ignoreUOMs.map(u => u.toLowerCase());\n\n  // Build dynamic regexes from i18n options\n  const groupHeaderRegex = buildPrefixPatternRegex(opts.groupHeaderPatterns);\n  const rangeSeparatorRegex = buildRangeSeparatorRegex(opts.rangeSeparators);\n  const stripPrefixRegex = buildStripPrefixRegex(opts.descriptionStripPrefixes);\n  const trailingContextRegex = buildTrailingContextRegex(opts.trailingQuantityContext);\n  const trailingQuantityRegex = buildTrailingQuantityRegex(opts.rangeSeparators);\n  const leadingQuantityPrefixRegex = buildLeadingQuantityPrefixRegex(opts.leadingQuantityPrefixes);\n\n  const uomStrings = opts.partialUnitMatching\n    ? collectUOMStrings(\n        Object.keys(opts.additionalUOMs).length > 0\n          ? buildUnitLookupMaps(opts.additionalUOMs)\n          : getDefaultUnitLookupMaps()\n      )\n    : [];\n\n  const ingredientArray = (\n    Array.isArray(ingredientText) ? ingredientText : ingredientText.split(newLineRegExp)\n  )\n    .map((line, index) => ({ line: line.trim(), sourceIndex: index }))\n    .filter(({ line }) => Boolean(line));\n\n  return ingredientArray.map(({ line, sourceIndex }) => {\n    const lineToParse = stripLeadingQuantityPrefixes(line, leadingQuantityPrefixRegex);\n    const oIng: Ingredient = {\n      quantity: null,\n      quantity2: null,\n      unitOfMeasureID: null,\n      unitOfMeasure: null,\n      description: '',\n      isGroupHeader: false,\n    };\n\n    if (opts.includeMeta) {\n      oIng.meta = {\n        sourceText: line,\n        sourceIndex,\n      };\n    }\n\n    // Check if the line begins with either (1) at least one numeric character, or\n    // (2) a decimal separator followed by at least one numeric character.\n    if (\n      lineToParse &&\n      (!isNaN(numericQuantity(lineToParse[0], nqOpts)) ||\n        (lineToParse[0] === opts.decimalSeparator &&\n          !isNaN(numericQuantity(lineToParse.slice(0, 2), nqOpts))))\n    ) {\n      // See how many of the first seven characters constitute a single value. This will be `quantity`.\n      let lenNum = 6;\n      let nqResult = NaN;\n\n      while (lenNum > 0 && isNaN(nqResult)) {\n        nqResult = numericQuantity(lineToParse.substring(0, lenNum).trim(), nqOpts);\n\n        if (nqResult > -1) {\n          oIng.quantity = nqResult;\n          oIng.description = lineToParse.substring(lenNum).trim();\n        }\n\n        lenNum--;\n      }\n    } else {\n      // The first character is not numeric. First check for trailing quantity/uom.\n      const trailingQtyResult = trailingQuantityRegex.exec(lineToParse);\n      const trailingQtyMaybeUom = trailingQtyResult?.at(-1)?.toLowerCase();\n\n      if (trailingQtyMaybeUom && ignoredUOMsLC.includes(trailingQtyMaybeUom)) {\n        // Trailing quantity detected, but bailing out since the UOM should be ignored.\n        oIng.description = lineToParse;\n      } else if (trailingQtyResult) {\n        // Trailing quantity detected with missing or non-ignored UOM.\n        // Remove the quantity and unit of measure from the description.\n        oIng.description = lineToParse.replace(trailingQuantityRegex, '').trim();\n\n        // Trailing quantity/range.\n        const firstQty = trailingQtyResult[3];\n        const secondQty = trailingQtyResult[12];\n        if (!firstQty) {\n          oIng.quantity = numericQuantity(secondQty, nqOpts);\n        } else {\n          oIng.quantity = numericQuantity(firstQty, nqOpts);\n          oIng.quantity2 = numericQuantity(secondQty, nqOpts);\n        }\n\n        // Trailing unit of measure.\n        const uomRaw = trailingQtyResult.at(-1);\n        if (uomRaw) {\n          let uomID: string | null = null;\n          let finalUomRaw = uomRaw;\n\n          // Greedy: try multi-word unit first (prefer longer match over shorter one)\n          if (oIng.description) {\n            const descWords = oIng.description.trim().split(/\\s+/);\n            if (descWords.length >= 1) {\n              const lastDescWord = descWords[descWords.length - 1];\n              const twoWordUnit = lastDescWord + ' ' + uomRaw;\n              const twoWordID = identifyUnit(twoWordUnit, options);\n\n              if (twoWordID) {\n                uomID = twoWordID;\n                finalUomRaw = twoWordUnit;\n                // Remove the last word from description\n                oIng.description = descWords.slice(0, -1).join(' ');\n              }\n            }\n          }\n\n          // Fall back to single-word match\n          if (!uomID) {\n            uomID = identifyUnit(uomRaw, options);\n            finalUomRaw = uomRaw;\n          }\n\n          if (uomID) {\n            oIng.unitOfMeasureID = uomID;\n            oIng.unitOfMeasure = opts.normalizeUOM ? uomID : finalUomRaw;\n          } else if (oIng.description.match(trailingContextRegex)) {\n            oIng.description += ` ${uomRaw}`;\n          }\n        }\n      } else {\n        // The first character is not numeric, and no trailing quantity was detected,\n        // so the entire line is the description.\n        oIng.description = lineToParse;\n\n        // If the line ends with \":\" or matches a group header pattern, it is assumed to be a group header.\n        if (oIng.description.endsWith(':') || groupHeaderRegex?.test(oIng.description)) {\n          oIng.isGroupHeader = true;\n        }\n      }\n    }\n\n    // Now check the description for a `quantity2` at the beginning.\n    // First we look for a dash, emdash, endash, or word separator to\n    // indicate a range, then process the next seven characters just\n    // like we did for `quantity`.\n    const q2reMatch = rangeSeparatorRegex.exec(oIng.description);\n    if (q2reMatch) {\n      const q2reMatchLen = q2reMatch[1].length;\n      const q2Portion = stripLeadingQuantityPrefixes(\n        oIng.description.substring(q2reMatchLen).trim(),\n        leadingQuantityPrefixRegex\n      );\n\n      // Guard against empty string after prefix stripping (e.g., aggressive\n      // prefixes could strip content down to nothing)\n      if (q2Portion) {\n        const nqResultFirstChar = numericQuantity(q2Portion[0], nqOpts);\n\n        if (!isNaN(nqResultFirstChar)) {\n          let lenNum = 7;\n          let nqResult = NaN;\n\n          while (--lenNum > 0 && isNaN(nqResult)) {\n            nqResult = numericQuantity(q2Portion.substring(0, lenNum), nqOpts);\n\n            if (!isNaN(nqResult)) {\n              oIng.quantity2 = nqResult;\n              oIng.description = q2Portion.substring(lenNum).trim();\n            }\n          }\n        }\n      }\n    }\n\n    // Check for a known unit of measure\n    const firstWordREMatches = firstWordRegEx.exec(oIng.description);\n\n    if (firstWordREMatches) {\n      const firstWord = firstWordREMatches[1].replace(/\\s+/g, ' ');\n      const remainingDesc = (firstWordREMatches[2] ?? '').trim();\n      if (remainingDesc) {\n        let uomID = identifyUnit(firstWord, options);\n        let matchedUnit = firstWord;\n        let finalDesc = remainingDesc;\n\n        // Try multi-word unit combinations (greedy matching: prefer longer matches over shorter ones)\n        const nextWords = remainingDesc.match(nextWordRegExp);\n        if (nextWords) {\n          const twoWordCombo = firstWord + ' ' + nextWords[1];\n          const twoWordID = identifyUnit(twoWordCombo, options);\n\n          // If multi-word unit exists, prefer it over single-word match.\n          // When no quantity is present, require remaining description\n          // (consistent with single-word behavior: \"1 cup\" → description, not UOM).\n          const multiWordFinalDesc = remainingDesc.substring(nextWords[0].length).trim();\n          if (twoWordID && (oIng.quantity !== null || multiWordFinalDesc)) {\n            uomID = twoWordID;\n            matchedUnit = twoWordCombo;\n            finalDesc = multiWordFinalDesc;\n          }\n        }\n\n        if (uomID) {\n          oIng.unitOfMeasureID = uomID;\n          oIng.unitOfMeasure = opts.normalizeUOM ? uomID : matchedUnit;\n          oIng.description = finalDesc;\n        }\n      }\n    }\n\n    // Fallback: scan description for known UOM substrings (for CJK/spaceless text)\n    if (!oIng.unitOfMeasureID && opts.partialUnitMatching && oIng.description) {\n      const descLower = oIng.description.toLowerCase();\n      for (const uomStr of uomStrings) {\n        const idx = descLower.indexOf(uomStr.toLowerCase());\n        if (idx === -1) continue;\n\n        const matchedText = oIng.description.substring(idx, idx + uomStr.length);\n        const uomID = identifyUnit(matchedText, options);\n        if (!uomID) continue;\n\n        const before = oIng.description.substring(0, idx).trim();\n        const after = oIng.description.substring(idx + uomStr.length).trim();\n        const newDesc = [before, after].filter(Boolean).join(' ');\n\n        // Don't extract UOM if it would leave description empty\n        // (consistent with \"2 cup\" keeping \"cup\" as description, not UOM)\n        if (!newDesc) continue;\n\n        oIng.unitOfMeasureID = uomID;\n        oIng.unitOfMeasure = opts.normalizeUOM ? uomID : matchedText;\n        oIng.description = newDesc;\n        break;\n      }\n    }\n\n    if (!opts.allowLeadingOf && stripPrefixRegex && oIng.description.match(stripPrefixRegex)) {\n      oIng.description = oIng.description.replace(stripPrefixRegex, '');\n    }\n\n    return oIng;\n  });\n};\n"],"mappings":"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"}