import { joinLines, splitLines } from "./profileCv"; import { TailoredCvDraft } from "./types"; const DEFAULT_SECTION_ORDER = ["summary", "skills", "experience", "education", "custom"]; export function emptyTailoredCvDraft(): TailoredCvDraft { return { templateId: "ats-minimal", headline: "", summary: [], selectedSkills: [], experience: [], education: [], customSections: [], renderOptions: { showPhoto: false, pageMode: "one-page", accentColor: "slate", sectionOrder: DEFAULT_SECTION_ORDER, bulletDensity: "balanced", }, status: "empty", renderedText: "", isLegacyFallback: false, }; } function formatDateRange(start?: string | null, end?: string | null, isCurrent?: boolean) { const normalizedStart = start?.trim(); const normalizedEnd = end?.trim(); if (!normalizedStart && !normalizedEnd) return ""; if (!normalizedStart) return normalizedEnd ?? ""; return `${normalizedStart} - ${isCurrent ? "Present" : normalizedEnd || "Present"}`; } export function renderTailoredCvDraftText(source?: Partial | null) { const draft = emptyTailoredCvDraft(); const sourceSummary = source?.summary; const sourceSelectedSkills = source?.selectedSkills; const sourceExperience = source?.experience; const sourceEducation = source?.education; const sourceCustomSections = source?.customSections; const summary = Array.isArray(sourceSummary) ? sourceSummary.filter(Boolean) : []; const selectedSkills = Array.isArray(sourceSelectedSkills) ? sourceSelectedSkills.filter(Boolean) : []; const experience = Array.isArray(sourceExperience) ? sourceExperience.filter(Boolean) : []; const education = Array.isArray(sourceEducation) ? sourceEducation.filter(Boolean) : []; const customSections = Array.isArray(sourceCustomSections) ? sourceCustomSections.filter(Boolean) : []; const normalized = { ...draft, ...source, summary, selectedSkills, experience, education, customSections, }; const sections: string[] = []; if (normalized.headline?.trim()) { sections.push(normalized.headline.trim()); } if (normalized.summary.length) { sections.push(`Professional Summary\n${normalized.summary.map((item) => `- ${item.trim()}`).join("\n")}`); } if (normalized.selectedSkills.length) { sections.push(`Core Skills\n${normalized.selectedSkills.map((item) => item.trim()).join("\n")}`); } if (normalized.experience.length) { const body = normalized.experience.map((item) => { const header = [item.title, item.company, item.location, formatDateRange(item.start, item.end, item.isCurrent)] .map((value) => value?.trim()) .filter(Boolean) .join(" | "); const bullets = (item.bullets ?? []).filter(Boolean).map((bullet) => `- ${bullet.trim()}`).join("\n"); return [header, bullets].filter(Boolean).join("\n"); }).filter(Boolean).join("\n\n"); if (body) sections.push(`Experience\n${body}`); } if (normalized.education.length) { const body = normalized.education.map((item) => { const header = [item.qualification, item.institution, item.location, formatDateRange(item.start, item.end, false)] .map((value) => value?.trim()) .filter(Boolean) .join(" | "); const details = (item.details ?? []).filter(Boolean).map((detail) => `- ${detail.trim()}`).join("\n"); return [header, details].filter(Boolean).join("\n"); }).filter(Boolean).join("\n\n"); if (body) sections.push(`Education\n${body}`); } normalized.customSections.forEach((section) => { const title = section.title?.trim() || "Additional Information"; const items = (section.items ?? []).filter(Boolean).map((item) => item.trim()).join("\n"); if (items) sections.push(`${title}\n${items}`); }); return sections.join("\n\n").trim(); } export function normalizeTailoredCvDraft(source?: Partial | null): TailoredCvDraft { const empty = emptyTailoredCvDraft(); const sourceSummary = source?.summary; const sourceSelectedSkills = source?.selectedSkills; const sourceExperience = source?.experience; const sourceEducation = source?.education; const sourceCustomSections = source?.customSections; const sourceRenderOptions = source?.renderOptions; const sourceSectionOrder = sourceRenderOptions?.sectionOrder; const normalized: TailoredCvDraft = { ...empty, ...source, templateId: source?.templateId?.trim() || empty.templateId, headline: source?.headline ?? "", summary: Array.isArray(sourceSummary) ? sourceSummary.filter(Boolean) : [], selectedSkills: Array.isArray(sourceSelectedSkills) ? sourceSelectedSkills.filter(Boolean) : [], experience: Array.isArray(sourceExperience) ? sourceExperience.map((item) => ({ title: item?.title ?? "", company: item?.company ?? "", location: item?.location ?? "", start: item?.start ?? "", end: item?.end ?? "", isCurrent: Boolean(item?.isCurrent), bullets: Array.isArray(item?.bullets) ? item.bullets.filter(Boolean) : [], })) : [], education: Array.isArray(sourceEducation) ? sourceEducation.map((item) => ({ qualification: item?.qualification ?? "", institution: item?.institution ?? "", location: item?.location ?? "", start: item?.start ?? "", end: item?.end ?? "", details: Array.isArray(item?.details) ? item.details.filter(Boolean) : [], })) : [], customSections: Array.isArray(sourceCustomSections) ? sourceCustomSections.map((item) => ({ title: item?.title ?? "", items: Array.isArray(item?.items) ? item.items.filter(Boolean) : [], })) : [], renderOptions: { ...empty.renderOptions, ...sourceRenderOptions, sectionOrder: Array.isArray(sourceSectionOrder) && sourceSectionOrder.length > 0 ? sourceSectionOrder.filter(Boolean) : DEFAULT_SECTION_ORDER, }, status: source?.status?.trim() || empty.status, renderedText: "", isLegacyFallback: Boolean(source?.isLegacyFallback), }; normalized.renderedText = renderTailoredCvDraftText(normalized); return normalized; } export { joinLines, splitLines };