import { TRUTH_CARD_NAMES } from './truthtrance-card-names'; import { parseFreeformFactV3 } from './truthtrance-freeform-facts-v3'; import type { CardCountFact } from './truthtrance-card-count'; import type { HandInventoryFact } from './truthtrance-hand-inventory'; /** * Recognize only new, unambiguous present-tense negation forms. Earlier * parsers remain unchanged for previously stamped questions. The caller * limits interpretation to fact-scoped questions and displays the result. */ export function parseFreeformFactV4(text: string): CardCountFact | HandInventoryFact | null { if (typeof text !== 'string' || text.length > 500) return null; const match = /^do[ \t]+you[ \t]+(?:have[ \t]+no|not[ \t]+have[ \t]+(a|an|any))[ \t]+(.+?)[ \t]*\??$/i.exec(text.trim()); if (!match) return null; const subject = match[2].trim(); // Reuse v3's bounded primary-role vocabulary and zero-count semantics. const category = parseFreeformFactV3(`Do you have exactly 0 ${subject}`); const inventory = category?.kind === 'handInventory' ? category : null; const nameInput = subject.replace(/[ \t]+in[ \t]+your[ \t]+hand$/i, '').trim(); if (inventory && /^(a|an)$/i.test(match[1] ?? '') && /s$/i.test(nameInput)) return null; const names = TRUTH_CARD_NAMES.filter(name => name.toLowerCase() === nameInput.toLowerCase()); // Refuse if the words could mean both a named face and a category. if ((inventory && names.length > 0) || names.length > 1) return null; if (inventory) return inventory; return names.length === 1 ? { kind: 'handCount', name: names[0], compare: 'eq', value: 0 } : null; }