import type { ConditionalRule, FormAnswers, Question } from './types' /** Returns true if the question should be shown given the current answers. */ export function isQuestionVisible( question: Question, answers: FormAnswers ): boolean { const rules = question.conditional_rules if (!rules || rules.length === 0) return true // All rules must pass (AND semantics) return rules.every((rule) => evaluateRule(rule, answers)) } function evaluateRule(rule: ConditionalRule, answers: FormAnswers): boolean { if (rule.op === 'eq') { return answers[rule.if_question] === rule.value } return false } /** * Returns the codes of questions that will become hidden when `triggerCode` * changes to `newValue`. Used to clear those answers reactively. */ export function getHiddenCodes( triggerCode: string, newValue: string | string[] | number | null, questions: Question[], currentAnswers: FormAnswers ): string[] { const projected = { ...currentAnswers, [triggerCode]: newValue } return questions .filter((q) => { if (!q.conditional_rules?.length) return false const dependsOnTrigger = q.conditional_rules.some( (r) => r.if_question === triggerCode ) if (!dependsOnTrigger) return false return !isQuestionVisible(q, projected) }) .map((q) => q.code) } /** All trigger codes in the survey (questions that have dependents). */ export function getTriggerCodes(questions: Question[]): Set { const triggers = new Set() for (const q of questions) { for (const rule of q.conditional_rules ?? []) { triggers.add(rule.if_question) } } return triggers }