// Project persistence in localStorage, plus migrations from older data shapes. import { makeId, indexKey, projectKey, loadProjectIndex, } from './bibleUtils.jsx'; // --------------------------------------------------------------------------- // Migration helpers (exported for testing) // --------------------------------------------------------------------------- export function migrateChunk(chunk) { if (chunk.observation !== undefined) { return { ...chunk, spilloverEndVerse: Number.isInteger(chunk.spilloverEndVerse) ? chunk.spilloverEndVerse : null, generalNotes: chunk.generalNotes ?? '', episodeNumber: chunk.episodeNumber ?? '', episodeTitle: chunk.episodeTitle ?? '', finalScript: chunk.finalScript ?? '', tags: chunk.tags ?? [], }; // already new format } return { ...chunk, observation: chunk.notes ?? '', interpretation: '', application: '', crossReferences: [], tags: [], spilloverEndVerse: null, generalNotes: '', episodeNumber: '', episodeTitle: '', finalScript: '', notes: undefined, }; } export function migrateProject(raw) { if (!raw) return null; // Already new format if (Array.isArray(raw.chapters)) { return { ...raw, chapters: raw.chapters.map((ch) => ({ ...ch, chunks: ch.chunks.map(migrateChunk), })), }; } // Old flat format → wrap in chapters array const id = raw.id ?? makeId(); return { id, title: raw.title ?? '', translation: raw.translation ?? 'BSB', lastEdited: raw.lastEdited ?? Date.now(), selectedChunkId: raw.selectedChunkId ?? null, chapters: [ { book: raw.book ?? '', bookAbbrev: raw.bookAbbrev ?? '', chapter: raw.chapter ?? '1', verses: raw.verses ?? [], chunks: (raw.chunks ?? []).map(migrateChunk), }, ], }; } // --------------------------------------------------------------------------- // Storage helpers // --------------------------------------------------------------------------- export function saveProjectToStorage(project) { // Honour an already-stamped lastEdited (e.g. from autosave) so localStorage // and the server always receive the identical timestamp. const updated = { ...project, lastEdited: project.lastEdited ?? Date.now() }; window.localStorage.setItem(projectKey(updated.id), JSON.stringify(updated)); const index = loadProjectIndex(); const existing = index.findIndex((e) => e.id === updated.id); const tags = Array.from( new Set( (updated.chapters ?? []) .flatMap((ch) => ch.chunks ?? []) .flatMap((chunk) => chunk.tags ?? []), ), ); const summary = { id: updated.id, title: updated.title, lastEdited: updated.lastEdited, chapterSummary: buildChapterSummary(updated), series: (updated.series ?? '').trim(), tags, }; if (existing >= 0) { index[existing] = summary; } else { index.push(summary); } window.localStorage.setItem(indexKey(), JSON.stringify(index)); return updated; // caller can use this exact object for the server PUT } export function deleteProjectFromStorage(id) { window.localStorage.removeItem(projectKey(id)); const index = loadProjectIndex().filter((e) => e.id !== id); window.localStorage.setItem(indexKey(), JSON.stringify(index)); } export function loadProjectById(id) { try { const raw = window.localStorage.getItem(projectKey(id)); return raw ? migrateProject(JSON.parse(raw)) : null; } catch { return null; } } export function migrateOldStorageKeys() { const keys = Object.keys(window.localStorage); keys.forEach((key) => { if (!key.startsWith('bible-study-') || key.startsWith('bible-study-index') || key.startsWith('bible-study-project')) return; try { const raw = JSON.parse(window.localStorage.getItem(key)); if (!raw || !raw.book) return; const migrated = migrateProject(raw); if (!migrated) return; saveProjectToStorage(migrated); window.localStorage.removeItem(key); } catch { // skip corrupt entries } }); } export function buildChapterSummary(project) { if (!Array.isArray(project.chapters)) return ''; return project.chapters .map((ch) => `${ch.book} ${ch.chapter}`) .join(', '); }