import type { JobsheetSection, Page } from "../types/session"; export type FlatPage = { sectionId: string; sectionIndex: number; sectionTitle?: string; pageIndex: number; page: Page; flatIndex: number; }; const MAX_PHOTOS_PRIMARY_PAGE = 2; const MAX_PHOTOS_CONTINUATION_PAGE = 6; function cloneTemplate(template?: Page["template"]): Page["template"] { if (!template) return undefined; return { ...template }; } function normalizePhotoIds(photoIds?: string[]): string[] { if (!Array.isArray(photoIds)) return []; return photoIds.filter((value) => Boolean(value)); } function buildPhotoContinuation(source: Page, photoIds: string[]): Page { return { items: [], template: cloneTemplate(source.template), photo_ids: photoIds, photo_layout: source.photo_layout, photo_order_locked: source.photo_order_locked, variant: "photos", }; } function splitPagePhotos(page: Page): Page[] { const items = Array.isArray(page.items) ? page.items : []; const normalized: Page = { ...page, items }; if (normalized.blank) { return [normalized]; } const photoIds = normalizePhotoIds(normalized.photo_ids); if (!photoIds.length) { return [normalized]; } if (normalized.variant === "photos") { const chunks: string[][] = []; for (let i = 0; i < photoIds.length; i += MAX_PHOTOS_CONTINUATION_PAGE) { chunks.push(photoIds.slice(i, i + MAX_PHOTOS_CONTINUATION_PAGE)); } if (chunks.length <= 1) { return [ { ...normalized, photo_ids: chunks[0] ?? [], variant: "photos", }, ]; } return chunks.map((chunk, idx) => { if (idx === 0) return { ...normalized, photo_ids: chunk, variant: "photos" }; return buildPhotoContinuation(normalized, chunk); }); } const baseChunk = photoIds.slice(0, MAX_PHOTOS_PRIMARY_PAGE); const extraIds = photoIds.slice(MAX_PHOTOS_PRIMARY_PAGE); const continuationChunks: string[][] = []; for (let i = 0; i < extraIds.length; i += MAX_PHOTOS_CONTINUATION_PAGE) { continuationChunks.push(extraIds.slice(i, i + MAX_PHOTOS_CONTINUATION_PAGE)); } const basePage: Page = { ...normalized, photo_ids: baseChunk, variant: normalized.variant ?? "full", }; if (!continuationChunks.length) { return [basePage]; } const extraPages = continuationChunks.map((chunk) => buildPhotoContinuation(normalized, chunk), ); return [basePage, ...extraPages]; } export function ensureSections(sections?: JobsheetSection[]): JobsheetSection[] { if (sections && sections.length) { return sections.map((section, index) => { const basePages = section.pages?.length ? section.pages : [{ items: [] }]; const normalizedPages = basePages.flatMap((page) => splitPagePhotos(page)); return { id: section.id, title: section.title ?? "Section", pages: normalizedPages.length ? normalizedPages : [{ items: [] }], }; }); } return [ { id: crypto.randomUUID(), title: "Section 1", pages: [{ items: [] }], }, ]; } export function flattenSections(sections: JobsheetSection[]): FlatPage[] { const result: FlatPage[] = []; sections.forEach((section, sectionIndex) => { const pages = section.pages ?? []; pages.forEach((page, pageIndex) => { result.push({ sectionId: section.id, sectionIndex, sectionTitle: section.title ?? `Section ${sectionIndex + 1}`, pageIndex, page, flatIndex: result.length, }); }); }); return result; } export function replacePage( sections: JobsheetSection[], sectionIndex: number, pageIndex: number, nextPage: Page, ): JobsheetSection[] { const next = sections.map((section, sIdx) => { if (sIdx !== sectionIndex) return section; const nextPages = [...(section.pages ?? [])]; nextPages[pageIndex] = nextPage; return { ...section, pages: nextPages }; }); return ensureSections(next); } export function insertPage( sections: JobsheetSection[], sectionIndex: number, pageIndex: number, page: Page, ): JobsheetSection[] { const next = sections.map((section, sIdx) => { if (sIdx !== sectionIndex) return section; const nextPages = [...(section.pages ?? [])]; const safeIndex = Math.max(0, Math.min(pageIndex, nextPages.length)); nextPages.splice(safeIndex, 0, page); return { ...section, pages: nextPages }; }); return ensureSections(next); } export function removePage( sections: JobsheetSection[], sectionIndex: number, pageIndex: number, ): JobsheetSection[] { const next = sections.map((section, sIdx) => { if (sIdx !== sectionIndex) return section; const nextPages = [...(section.pages ?? [])]; if (nextPages.length <= 1) return section; nextPages.splice(pageIndex, 1); return { ...section, pages: nextPages.length ? nextPages : [{ items: [] }] }; }); return ensureSections(next); }