export type DeskValueField = { type: string; options?: Record }; export type DeskAnswer = string | number | boolean | string[] | null; export function deskScalar(value: unknown): string { if (typeof value === 'string' || typeof value === 'number') return String(value); if (typeof value === 'boolean') return value ? '1' : ''; return ''; } export function deskChoices(value: unknown): string[] { return Array.isArray(value) && value.every((v) => typeof v === 'string') ? value : []; } export function deskEmpty(value: unknown): boolean { return ( value === null || value === undefined || (typeof value === 'string' && value.trim() === '') || (Array.isArray(value) && value.length === 0) ); } /** Preserve typed answers and refusals; a master/profile can never supply a consent declaration. */ export function mergeDeskPrefill( current: Record, patch: Record, fields: Record, replace = false, ): Record { const next = { ...current }; for (const [key, value] of Object.entries(patch)) { const field = fields[key]; if ( field?.type === 'consent' || field?.type === 'attachment' || (!replace && !deskEmpty(current[key])) ) continue; if (field?.type === 'multiselect') { if ( Array.isArray(value) && value.every( (v) => typeof v === 'string' && Object.hasOwn(field.options ?? {}, v), ) ) next[key] = [...value]; } else if ( value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' ) next[key] = value; } return next; }