import type { FormElementDto, FormElementSectionDto, SectionSpawnTriggerDto } from '~~/.api-client' import { VisibilityConditionOperator, VisibilityConditionType } from '~~/.api-client' export function useSectionSpawning() { function processSpawnTriggers( sections: FormElementSectionDto[], updatedFormElements: FormElementDto[] ): FormElementSectionDto[] { let resultSections = sections for (const formElement of updatedFormElements) { const triggers = formElement.sectionSpawnTriggers if (!triggers || triggers.length === 0 || !formElement.reference) { continue } const triggerValue = getFormElementValue(formElement) // Process each trigger independently for (const trigger of triggers) { resultSections = processSingleTrigger(resultSections, formElement, trigger, triggerValue) } } return resultSections } function processSingleTrigger( sections: FormElementSectionDto[], formElement: FormElementDto, trigger: SectionSpawnTriggerDto, triggerValue: string ): FormElementSectionDto[] { let resultSections = sections const shouldSpawn = shouldSpawnSection(trigger, triggerValue) // Find existing spawned section for this specific trigger (by template reference) const existingSpawnedSection = findSpawnedSectionForTrigger( resultSections, formElement.reference!, trigger.templateReference ) // Handle three spawn states: // 1. Condition met but no section spawned yet → create new section if (shouldSpawn && !existingSpawnedSection) { resultSections = spawnNewSection(resultSections, formElement, trigger, triggerValue) } // 2. Condition no longer met but section exists → remove spawned section else if (!shouldSpawn && existingSpawnedSection) { resultSections = removeSpawnedSectionForTrigger(resultSections, formElement.reference!, trigger.templateReference) } // 3. Condition still met and section exists → update section titles if value changed else if (shouldSpawn && existingSpawnedSection && triggerValue) { resultSections = updateSpawnedSectionTitles(resultSections, formElement.reference!, trigger, triggerValue) } return resultSections } function spawnNewSection( sections: FormElementSectionDto[], element: FormElementDto, trigger: SectionSpawnTriggerDto, triggerValue: string ): FormElementSectionDto[] { const templateSection = findTemplateSection(sections, trigger.templateReference) if (!templateSection) { return sections } const newSection = spawnSectionFromTemplate(templateSection, element.reference!, triggerValue) // Find template index const templateIndex = sections.findIndex((s) => s.isTemplate && s.templateReference === trigger.templateReference) if (templateIndex === -1) { // Fallback: append if template not found (shouldn't happen) return sections.concat(newSection as FormElementSectionDto) } // Find insertion position: after template and after any existing spawned sections from same template let insertionIndex = templateIndex + 1 while (insertionIndex < sections.length) { const section = sections[insertionIndex]! if (section.isTemplate || section.templateReference !== trigger.templateReference) { break } insertionIndex++ } // Insert at calculated position const result = [...sections] result.splice(insertionIndex, 0, newSection as FormElementSectionDto) return result } function updateSpawnedSectionTitles( sections: FormElementSectionDto[], elementReference: string, trigger: SectionSpawnTriggerDto, triggerValue: string ): FormElementSectionDto[] { const template = findTemplateSection(sections, trigger.templateReference) if (!template) { return sections } const hasTitleTemplate = template.titleTemplate const hasShortTitleTemplate = template.shortTitle?.includes('{{triggerValue}}') const hasDescriptionTemplate = template.description?.includes('{{triggerValue}}') if (!hasTitleTemplate && !hasShortTitleTemplate && !hasDescriptionTemplate) { return sections } return sections.map((section) => { if (section.spawnedFromElementReference === elementReference && !section.isTemplate) { const sectionUpdate: Partial = {} if (hasTitleTemplate) { sectionUpdate.title = interpolateTitle(template.titleTemplate!, triggerValue) } if (hasShortTitleTemplate && template.shortTitle) { sectionUpdate.shortTitle = interpolateTitle(template.shortTitle, triggerValue) } if (hasDescriptionTemplate && template.description) { sectionUpdate.description = interpolateTitle(template.description, triggerValue) } return { ...section, ...sectionUpdate } } return section }) } function findSpawnedSectionForTrigger( sections: FormElementSectionDto[], elementReference: string, templateReference: string ): FormElementSectionDto | undefined { return sections.find( (section) => !section.isTemplate && section.spawnedFromElementReference === elementReference && section.templateReference === templateReference ) } function removeSpawnedSectionForTrigger( sections: FormElementSectionDto[], elementReference: string, templateReference: string ): FormElementSectionDto[] { return sections.filter( (section) => section.isTemplate || section.spawnedFromElementReference !== elementReference || section.templateReference !== templateReference ) } function spawnSectionFromTemplate( templateSection: FormElementSectionDto, triggerElementReference: string, triggerValue: string ): FormElementSectionDto { const clonedSection = JSON.parse(JSON.stringify(templateSection)) as FormElementSectionDto const title = templateSection.titleTemplate ? interpolateTitle(templateSection.titleTemplate, triggerValue) : templateSection.title const shortTitle = templateSection.shortTitle?.includes('{{triggerValue}}') ? interpolateTitle(templateSection.shortTitle, triggerValue) : templateSection.shortTitle const description = templateSection.description?.includes('{{triggerValue}}') ? interpolateTitle(templateSection.description, triggerValue) : templateSection.description return { ...clonedSection, id: undefined, applicationFormId: undefined, title, shortTitle, description, isTemplate: false, spawnedFromElementReference: triggerElementReference, formElementSubSections: clonedSection.formElementSubSections.map((subsection) => ({ ...subsection, id: undefined, formElementSectionId: undefined, formElements: subsection.formElements.map((element) => ({ ...element, id: undefined, formElementSubSectionId: undefined })) })) } } function shouldSpawnSection(trigger: SectionSpawnTriggerDto, triggerElementValue: string): boolean { const operator = trigger.sectionSpawnOperator || VisibilityConditionOperator.Equals const isConditionMet = evaluateCondition(triggerElementValue, trigger.sectionSpawnExpectedValue || '', operator) return trigger.sectionSpawnConditionType === VisibilityConditionType.Show ? isConditionMet : !isConditionMet } function findTemplateSection( sections: FormElementSectionDto[], templateReference: string ): FormElementSectionDto | undefined { return sections.find((section) => section.isTemplate && section.templateReference === templateReference) } function evaluateCondition( actualValue: string, expectedValue: string, operator: VisibilityConditionOperator ): boolean { switch (operator) { case VisibilityConditionOperator.Equals: return actualValue.toLowerCase() === expectedValue.toLowerCase() case VisibilityConditionOperator.NotEquals: return actualValue.toLowerCase() !== expectedValue.toLowerCase() case VisibilityConditionOperator.IsEmpty: return actualValue === '' case VisibilityConditionOperator.IsNotEmpty: return actualValue !== '' default: return false } } function interpolateTitle(titleTemplate: string, triggerValue: string): string { return titleTemplate.replace(/\{\{triggerValue\}\}/g, triggerValue) } function getFormElementValue(element: FormElementDto): string { if (element.type === 'TEXTAREA' || element.type === 'TEXTFIELD') { return element.options[0]?.value || '' } const selectedOption = element.options.find((option) => option.value === 'true') return selectedOption?.label || '' } return { processSpawnTriggers } }