import { z } from 'zod' const reservedOptionValues = new Set([ 'answer', 'answers', 'label', 'option', 'options', 'question', ]) function normalized(value: string) { return value.trim().toLocaleLowerCase().replace(/\s+/g, ' ') } export const CoauthorQuestionSchema = z.object({ question: z.string().trim().min(5).max(300), options: z.array(z.string().trim().min(2).max(100)).length(3), }).superRefine((value, context) => { const question = normalized(value.question) const seen = new Set() value.options.forEach((option, index) => { const candidate = normalized(option) if (!/[\p{L}\p{N}]/u.test(candidate) || reservedOptionValues.has(candidate)) { context.addIssue({ code: 'custom', message: 'Each option must be a meaningful answer.', path: ['options', index], }) } if (seen.has(candidate)) { context.addIssue({ code: 'custom', message: 'Answer options must be distinct.', path: ['options', index], }) } if (candidate === question || (candidate.length >= 20 && question.includes(candidate))) { context.addIssue({ code: 'custom', message: 'An answer option must not repeat the question.', path: ['options', index], }) } seen.add(candidate) }) }) export const StoredCoauthorQuestionSchema = z.object({ id: z.string().trim().min(1).max(80), label: z.string().trim().min(5).max(300), options: CoauthorQuestionSchema.shape.options, }).superRefine((value, context) => { const result = CoauthorQuestionSchema.safeParse({ question: value.label, options: value.options }) for (const issue of result.error?.issues ?? []) { context.addIssue({ code: 'custom', message: issue.message, path: issue.path[0] === 'question' ? ['label', ...issue.path.slice(1)] : issue.path, }) } })