import { api } from "./api"; // Mirrors the backend CvVariantSettings (the lens over the master career profile). export type CvSectionSetting = { key: string; hidden?: boolean; title?: string; itemOrder?: string[] }; export type CvItemOverride = { hidden?: boolean; title?: string; subtitle?: string; bullets?: string[] }; export type CvCustomSectionSetting = { key: string; title?: string; items: string[]; hidden?: boolean }; export type CvVariantSettings = { themeId: string; accentColor?: string | null; headingFont?: string | null; bodyFont?: string | null; density?: string | null; pageSize?: string | null; dateFormat?: string | null; language?: string | null; headline?: string | null; showPhoto: boolean; showPageNumbers: boolean; showIcons: boolean; sections: CvSectionSetting[]; overrides: Record; customSections: CvCustomSectionSetting[]; }; export type CvTheme = { id: string; name: string; category: string; description: string; layout: string; accent: string; photoShape: string; supportsIcons: boolean; atsFriendly: boolean; premium: boolean; available: boolean; swatches: string[]; }; // Master profile resolved to sections+entries (GET /api/cv/outline) — what the Content tab edits. export type CvOutlineEntry = { key?: string; title?: string; subtitle?: string; meta?: string; bullets: string[]; tags: string[] }; export type CvOutlineSection = { key: string; title: string; kind: "bullets" | "tags" | "entries"; bullets: string[]; tags: string[]; entries: CvOutlineEntry[] }; export type CvOutline = { fullName: string; headline?: string; sections: CvOutlineSection[] }; export type CvVariantSummary = { id: number; name: string; themeId: string; publicSlug: string; isPublic: boolean; version: number; jobApplicationId: number | null; updatedAtUtc: string; }; export type CvVariant = { id: number; name: string; settings: CvVariantSettings; isPublic: boolean; publicSlug: string; version: number; jobApplicationId: number | null; updatedAtUtc: string; }; export type CvVariantVersionInfo = { version: number; source: string; createdAtUtc: string; isCurrent: boolean }; export type CvRender = { themeId: string; html: string; suggestedFileName: string }; export const AI_ACTIONS: { key: string; label: string }[] = [ { key: "improve", label: "Improve writing" }, { key: "professional", label: "Professional tone" }, { key: "shorten", label: "Shorten" }, { key: "expand", label: "Expand" }, { key: "grammar", label: "Fix grammar" }, { key: "ats", label: "ATS optimise" }, { key: "bullets", label: "Generate bullets" }, { key: "summary", label: "Generate summary" }, { key: "rewrite", label: "Rewrite" }, { key: "tailor", label: "Tailor to role" }, ]; export const DEFAULT_SECTION_ORDER = [ "summary", "experience", "education", "projects", "skills", "certifications", "languages", "interests", ]; export const SECTION_LABELS: Record = { summary: "Professional Summary", experience: "Experience", education: "Education", projects: "Projects", skills: "Skills", certifications: "Certifications", languages: "Languages", interests: "Interests", }; export function emptyCvVariantSettings(themeId = "modern"): CvVariantSettings { return { themeId, showPhoto: false, showPageNumbers: false, showIcons: true, sections: [], overrides: {}, customSections: [], }; } // --- API --- // --- Pure helpers (unit-tested) --- // Move an array item from one index to another, returning a new array. export function moveItem(arr: T[], from: number, to: number): T[] { if (from === to || from < 0 || to < 0 || from >= arr.length || to >= arr.length) return arr; const next = [...arr]; const [item] = next.splice(from, 1); next.splice(to, 0, item); return next; } // Wrap the [start,end) selection of `text` with markdown markers, for the rich-text toolbar. // Returns the new text and the selection to restore. If nothing is selected, inserts a placeholder. export function wrapSelection( text: string, start: number, end: number, before: string, after: string, placeholder = "text", ): { text: string; selStart: number; selEnd: number } { const selected = text.slice(start, end) || placeholder; const next = text.slice(0, start) + before + selected + after + text.slice(end); return { text: next, selStart: start + before.length, selEnd: start + before.length + selected.length }; } export const cvBuilderApi = { themes: () => api.get("/cv/themes").then((r) => r.data), outline: () => api.get("/cv/outline").then((r) => r.data), list: () => api.get("/cv/variants").then((r) => r.data), create: (body: { name?: string; jobApplicationId?: number | null; settings?: CvVariantSettings }) => api.post("/cv/variants", body).then((r) => r.data), get: (id: number) => api.get(`/cv/variants/${id}`).then((r) => r.data), save: (id: number, body: { name?: string; settings: CvVariantSettings; source?: string }) => api.put(`/cv/variants/${id}`, body).then((r) => r.data), remove: (id: number) => api.delete(`/cv/variants/${id}`), duplicate: (id: number, name?: string) => api.post(`/cv/variants/${id}/duplicate`, { name }).then((r) => r.data), setPublic: (id: number, isPublic: boolean) => api.put(`/cv/variants/${id}/public`, { isPublic }).then((r) => r.data), versions: (id: number) => api.get(`/cv/variants/${id}/versions`).then((r) => r.data), restore: (id: number, version: number) => api.post(`/cv/variants/${id}/versions/${version}/restore`).then((r) => r.data), previewSettings: (settings: CvVariantSettings) => api.post("/cv/preview", { settings }).then((r) => r.data), aiAssist: (body: { action: string; text: string; role?: string; language?: string; context?: string }) => api.post<{ original: string; result: string }>("/cv/ai/assist", body).then((r) => r.data), exportPdfUrl: (id: number) => `/cv/variants/${id}/export-pdf`, };