import type { Field, NameSubField, AddressSubField } from '@/types/field' import { assignLegacyNameFieldKeysIfMissing, getChildFieldSettings, isNameFieldKey, resolveNameFieldKey, sortChildrenByDisplayOrder, } from '@/utils/nameFieldKeyUtils' function safeParse(json: string | null | undefined): Record { if (!json) return {} try { return JSON.parse(json) as Record } catch { return {} } } export function normalizeFieldsForBuilder(apiFields: Field[]): Field[] { if (!Array.isArray(apiFields) || apiFields.length === 0) return [] const parents: Field[] = [] const childrenByParent: Record = {} for (const f of apiFields) { // Parse settings if it's a string (from database) if (typeof f.settings === 'string') { const parsed = safeParse(f.settings) f.settings = parsed as Record } if (f.parentId && typeof f.parentId === 'number' && f.parentId > 0) { if (!childrenByParent[f.parentId]) childrenByParent[f.parentId] = [] childrenByParent[f.parentId].push(f) } else { parents.push(f) } } // Ensure fields have proper row/column defaults parents.forEach((field, index) => { // Only set defaults if ALL layout properties are missing (backward compatibility) const hasLayoutInfo = (field.rowIndex !== undefined && field.rowIndex !== null) || (field.columnIndex !== undefined && field.columnIndex !== null) || (field.width !== undefined && field.width !== null) if (!hasLayoutInfo) { // Old form without layout info - each field in its own row field.rowIndex = index field.columnIndex = 0 field.width = 100 } else { // Form with layout info - preserve existing values or use sensible defaults if (field.rowIndex === undefined || field.rowIndex === null) { field.rowIndex = 0 } if (field.columnIndex === undefined || field.columnIndex === null) { field.columnIndex = 0 } if (field.width === undefined || field.width === null) { field.width = 100 } } // Extract pageId from settings if it exists if (field.settings && typeof field.settings === 'object' && 'pageId' in field.settings) { const settingsObj = field.settings as Record if (settingsObj.pageId && typeof settingsObj.pageId === 'string') { field.pageId = settingsObj.pageId } } }) return parents.map((parent) => { if (parent.type === 'name') { const childrenList = childrenByParent[parent.id] ?? [] const childOrder = new Map() childrenList.forEach((child, index) => childOrder.set(child, index)) const children = sortChildrenByDisplayOrder(childrenList, childOrder) const childSettingsList = children.map((child) => getChildFieldSettings(child)) const nameFields: NameSubField[] = assignLegacyNameFieldKeysIfMissing( children.map((child, idx) => { const settings = childSettingsList[idx] const label = (child as Partial).label ?? String(settings.label ?? '') return { type: isNameFieldKey(child.nameFieldType) ? child.nameFieldType : undefined, id: String(child.id ?? ''), modelValue: (child as Partial).defaultValue ?? '', label, required: !!(child as Partial).required || Boolean(settings.required), optionHide: !!(child as Partial).hideLabel || Boolean(settings.hideLabel), description: String( (child as Partial).description ?? settings.description ?? '', ), placeholder: String( (child as Partial).placeholder ?? settings.placeholder ?? '', ), } }), childSettingsList, ) const nameFieldTypes = nameFields.map((subfield) => subfield.type) return { ...parent, nameFields, nameFieldTypes, // Preserve grid layout properties rowIndex: parent.rowIndex ?? 0, columnIndex: parent.columnIndex ?? 0, width: parent.width ?? 100, } as Field } if (parent.type === 'address') { const children = childrenByParent[parent.id] ?? [] const defaultAddressOrder = [ 'streetAddress', 'addressLine2', 'city', 'state', 'zip', 'country', ] const addressFields: AddressSubField[] = children.map((child, idx) => { // Handle both string and object settings let s: Record if (typeof child.settings === 'string') { s = safeParse(child.settings) } else if (typeof child.settings === 'object' && child.settings !== null) { s = child.settings as Record } else { s = {} } const explicitType = (child as Partial).addressType || String(s['type'] ?? '') const type = explicitType && explicitType.trim() !== '' ? explicitType : (defaultAddressOrder[idx] ?? `addressPart${idx + 1}`) return { type, id: String(child.id ?? ''), label: (child as Partial).label ?? String(s['label'] ?? ''), value: (child as Partial).defaultValue ?? String(s['value'] ?? ''), placeholder: (child as Partial).placeholder ?? String(s['placeholder'] ?? ''), required: !!(child as Partial).required || Boolean(s['required']), hideLabel: !!(child as Partial).hideLabel || Boolean(s['hideLabel']), description: String((child as Partial).description ?? s['description'] ?? ''), requiredMessage: String( (child as Partial).requiredMessage ?? s['requiredMessage'] ?? '', ), visible: typeof (child as Partial).visible === 'boolean' ? Boolean((child as Partial).visible) : Boolean(s['visible'] ?? true), } }) const addressFieldTypes = addressFields.map((af) => af.type) return { ...parent, addressFields, addressFieldTypes, // Preserve grid layout properties rowIndex: parent.rowIndex ?? 0, columnIndex: parent.columnIndex ?? 0, width: parent.width ?? 100, } as Field } // Regular field - preserve grid layout properties and extract settings const settings = safeParse((parent as Field & { settings?: string }).settings) return { ...parent, htmlContent: (parent as Partial).htmlContent ?? String(settings['htmlContent'] ?? ''), rowIndex: parent.rowIndex ?? 0, columnIndex: parent.columnIndex ?? 0, width: parent.width ?? 100, } }) } export function denormalizeFieldsForApi(fields: Field[]): Field[] { const result: Field[] = [] for (const field of fields) { if (field.type === 'name') { if (Array.isArray(field.nameFields)) { const { nameFields, ...parentRest } = field // Preserve row/column/width layout for name fields const parentSettings = field.pageId ? { pageId: field.pageId } : {} result.push({ ...parentRest, type: 'name', rowIndex: field.rowIndex, columnIndex: field.columnIndex, width: field.width, settings: parentSettings, } as Field) nameFields.forEach((sub, idx) => { const nameFieldType = isNameFieldKey(sub.type) ? sub.type : resolveNameFieldKey(sub, undefined, idx) result.push({ id: sub.id ? parseInt(sub.id, 10) || 0 : 0, formId: field.formId, fieldIndex: field.fieldIndex, type: 'text', label: sub.label, defaultValue: sub.modelValue, required: sub.required, placeholder: sub.placeholder, position: field.position, value: sub.modelValue, hideLabel: sub.optionHide, description: sub.description, requiredMessage: '', parentId: field.id, nameFieldType, subFieldIndex: idx, pageId: field.pageId, settings: { nameFieldType, subFieldIndex: idx, ...(field.pageId && { pageId: field.pageId }), }, } as Field) }) continue } } if (field.type === 'address') { if (Array.isArray(field.addressFields)) { const { addressFields, ...parentRest } = field // Preserve row/column/width layout for address fields const parentSettings = field.pageId ? { pageId: field.pageId } : {} result.push({ ...parentRest, rowIndex: field.rowIndex, columnIndex: field.columnIndex, width: field.width, settings: parentSettings, } as Field) addressFields.forEach((sub, idx) => { result.push({ id: sub.id ? parseInt(sub.id, 10) || 0 : 0, formId: field.formId, fieldIndex: field.fieldIndex, type: 'text', label: sub.label, defaultValue: sub.value, required: sub.required, placeholder: sub.placeholder, position: field.position, value: sub.value, hideLabel: sub.hideLabel, description: sub.description, requiredMessage: sub.requiredMessage, parentId: field.id, addressType: sub.type, subFieldIndex: idx, visible: sub.visible, pageId: field.pageId, settings: { hideLabel: sub.hideLabel, description: sub.description, placeholder: sub.placeholder, type: sub.type, requiredMessage: sub.requiredMessage, visible: typeof sub.visible === 'boolean' ? sub.visible : true, ...(field.pageId && { pageId: field.pageId }), }, } as Field) }) continue } } // Regular field - preserve row/column/width layout const settings = field.pageId ? { pageId: field.pageId } : {} result.push({ ...field, id: typeof field.id === 'string' ? parseInt(field.id, 10) || 0 : field.id || 0, rowIndex: field.rowIndex, columnIndex: field.columnIndex, width: field.width, settings, } as Field) } return result }