const OBJECT_OBJECT = "[object Object]"; export function isObjectObject(value: string | undefined | null): boolean { return value?.trim() === OBJECT_OBJECT; } /** Extract human-readable text from AI fields that may be strings or nested objects. */ export function extractText(value: unknown): string { if (value == null) return ""; if (typeof value === "string") { const trimmed = value.trim(); return isObjectObject(trimmed) ? "" : trimmed; } if (typeof value === "number" || typeof value === "boolean") { return String(value); } if (Array.isArray(value)) { return value.map((item) => extractText(item)).filter(Boolean).join("; "); } if (typeof value === "object") { const o = value as Record; const parts: string[] = []; for (const key of ["task", "text", "description", "content", "summary", "goal", "step"]) { const part = extractText(o[key]); if (part) parts.push(part); } if (Array.isArray(o.steps)) { const steps = o.steps.map((s) => extractText(s)).filter(Boolean); if (steps.length) parts.push(steps.join("; ")); } if (Array.isArray(o.instructions)) { const instructions = o.instructions.map((s) => extractText(s)).filter(Boolean); if (instructions.length) parts.push(instructions.join("; ")); } return parts.join(" — ").trim(); } return ""; } export function extractStringList(value: unknown): string[] { if (!Array.isArray(value)) return []; return value.map((item) => extractText(item)).filter(Boolean); } export function normalizeQuizAnswer( rawAnswer: unknown, options: string[] ): { answerIndex: number; answerText: string } { if (options.length === 0) { const text = extractText(rawAnswer); return { answerIndex: 0, answerText: text || "See explanation" }; } if (typeof rawAnswer === "number" && Number.isFinite(rawAnswer)) { const idx = Math.min(Math.max(0, rawAnswer), options.length - 1); return { answerIndex: idx, answerText: options[idx] ?? "" }; } const asString = extractText(rawAnswer).trim(); if (!asString) return { answerIndex: 0, answerText: options[0] ?? "" }; const asNum = Number(asString); if (Number.isFinite(asNum) && asNum >= 0 && asNum < options.length) { return { answerIndex: asNum, answerText: options[asNum] ?? "" }; } const letterMatch = /^[A-Da-d]$/.exec(asString); if (letterMatch) { const idx = letterMatch[0].toUpperCase().charCodeAt(0) - 65; if (idx >= 0 && idx < options.length) { return { answerIndex: idx, answerText: options[idx] ?? "" }; } } const optionIdx = options.findIndex( (opt) => opt.toLowerCase() === asString.toLowerCase() ); if (optionIdx >= 0) { return { answerIndex: optionIdx, answerText: options[optionIdx] ?? "" }; } return { answerIndex: 0, answerText: asString }; }