import { SyncedBook, SyncedChapter, SyncedChapterContent, SyncedChapterInfo, SyncedCharacter, SyncedCharacterAttribute, SyncedLocation, SyncedLocationElement, SyncedLocationSubElement, SyncedWorld, SyncedWorldElement, SyncedIncident, SyncedPlotPoint, SyncedIssue, SyncedActSummary, SyncedGuideLine, SyncedAIGuideLine } from "@/lib/models/SyncedBook"; /** * Résultat de comparaison pour un livre */ export interface BookSyncDiff { id: string; type: string; title: string; subTitle: string | null; bookNeedsUpdate: boolean; // Le livre lui-même a changé lastUpdate: number; chapters: SyncedChapter[]; characters: SyncedCharacter[]; locations: SyncedLocation[]; worlds: SyncedWorld[]; incidents: SyncedIncident[]; plotPoints: SyncedPlotPoint[]; issues: SyncedIssue[]; actSummaries: SyncedActSummary[]; guideLine: SyncedGuideLine | null; aiGuideLine: SyncedAIGuideLine | null; hasAnyChanges: boolean; // Indique si des changements existent } /** * Compare en profondeur deux livres synchronisés et retourne les différences */ export function compareBooks(serverBook: SyncedBook, localBook: SyncedBook): BookSyncDiff { const bookNeedsUpdate = serverBook.lastUpdate > localBook.lastUpdate; // Comparer les chapitres const chaptersWithChanges: SyncedChapter[] = []; for (const serverChapter of serverBook.chapters) { const localChapter = localBook.chapters.find(c => c.id === serverChapter.id); if (!localChapter) { // Chapitre n'existe pas localement chaptersWithChanges.push(serverChapter); } else if (serverChapter.lastUpdate > localChapter.lastUpdate) { // Chapitre a changé chaptersWithChanges.push(serverChapter); } else { // Vérifier les contenus du chapitre const contentsWithChanges: SyncedChapterContent[] = []; for (const serverContent of serverChapter.contents) { const localContent = localChapter.contents.find(c => c.id === serverContent.id); if (!localContent || serverContent.lastUpdate > localContent.lastUpdate) { contentsWithChanges.push(serverContent); } } // Vérifier l'info du chapitre let infoNeedsUpdate = false; if (serverChapter.info && localChapter.info) { infoNeedsUpdate = serverChapter.info.lastUpdate > localChapter.info.lastUpdate; } else if (serverChapter.info && !localChapter.info) { infoNeedsUpdate = true; } // Si des contenus ou info ont changé, inclure le chapitre if (contentsWithChanges.length > 0 || infoNeedsUpdate) { chaptersWithChanges.push({ ...serverChapter, contents: contentsWithChanges.length > 0 ? contentsWithChanges : serverChapter.contents, info: infoNeedsUpdate ? serverChapter.info : null }); } } } // Comparer les personnages const charactersWithChanges: SyncedCharacter[] = []; for (const serverChar of serverBook.characters) { const localChar = localBook.characters.find(c => c.id === serverChar.id); if (!localChar) { charactersWithChanges.push(serverChar); } else if (serverChar.lastUpdate > localChar.lastUpdate) { charactersWithChanges.push(serverChar); } else { // Vérifier les attributs const attributesWithChanges: SyncedCharacterAttribute[] = []; for (const serverAttr of serverChar.attributes) { const localAttr = localChar.attributes.find(a => a.id === serverAttr.id); if (!localAttr || serverAttr.lastUpdate > localAttr.lastUpdate) { attributesWithChanges.push(serverAttr); } } if (attributesWithChanges.length > 0) { charactersWithChanges.push({ ...serverChar, attributes: attributesWithChanges }); } } } // Comparer les locations const locationsWithChanges: SyncedLocation[] = []; for (const serverLoc of serverBook.locations) { const localLoc = localBook.locations.find(l => l.id === serverLoc.id); if (!localLoc) { locationsWithChanges.push(serverLoc); } else if (serverLoc.lastUpdate > localLoc.lastUpdate) { locationsWithChanges.push(serverLoc); } else { // Vérifier les éléments const elementsWithChanges: SyncedLocationElement[] = []; for (const serverElem of serverLoc.elements) { const localElem = localLoc.elements.find(e => e.id === serverElem.id); if (!localElem) { elementsWithChanges.push(serverElem); } else if (serverElem.lastUpdate > localElem.lastUpdate) { elementsWithChanges.push(serverElem); } else { // Vérifier les sous-éléments const subElementsWithChanges: SyncedLocationSubElement[] = []; for (const serverSubElem of serverElem.subElements) { const localSubElem = localElem.subElements.find(s => s.id === serverSubElem.id); if (!localSubElem || serverSubElem.lastUpdate > localSubElem.lastUpdate) { subElementsWithChanges.push(serverSubElem); } } if (subElementsWithChanges.length > 0) { elementsWithChanges.push({ ...serverElem, subElements: subElementsWithChanges }); } } } if (elementsWithChanges.length > 0) { locationsWithChanges.push({ ...serverLoc, elements: elementsWithChanges }); } } } // Comparer les mondes const worldsWithChanges: SyncedWorld[] = []; for (const serverWorld of serverBook.worlds) { const localWorld = localBook.worlds.find(w => w.id === serverWorld.id); if (!localWorld) { worldsWithChanges.push(serverWorld); } else if (serverWorld.lastUpdate > localWorld.lastUpdate) { worldsWithChanges.push(serverWorld); } else { // Vérifier les éléments du monde const elementsWithChanges: SyncedWorldElement[] = []; for (const serverElem of serverWorld.elements) { const localElem = localWorld.elements.find(e => e.id === serverElem.id); if (!localElem || serverElem.lastUpdate > localElem.lastUpdate) { elementsWithChanges.push(serverElem); } } if (elementsWithChanges.length > 0) { worldsWithChanges.push({ ...serverWorld, elements: elementsWithChanges }); } } } // Comparer les incidents const incidentsWithChanges: SyncedIncident[] = []; for (const serverIncident of serverBook.incidents) { const localIncident = localBook.incidents.find(i => i.id === serverIncident.id); if (!localIncident || serverIncident.lastUpdate > localIncident.lastUpdate) { incidentsWithChanges.push(serverIncident); } } // Comparer les plot points const plotPointsWithChanges: SyncedPlotPoint[] = []; for (const serverPlot of serverBook.plotPoints) { const localPlot = localBook.plotPoints.find(p => p.id === serverPlot.id); if (!localPlot || serverPlot.lastUpdate > localPlot.lastUpdate) { plotPointsWithChanges.push(serverPlot); } } // Comparer les issues const issuesWithChanges: SyncedIssue[] = []; for (const serverIssue of serverBook.issues) { const localIssue = localBook.issues.find(i => i.id === serverIssue.id); if (!localIssue || serverIssue.lastUpdate > localIssue.lastUpdate) { issuesWithChanges.push(serverIssue); } } // Comparer les act summaries const actSummariesWithChanges: SyncedActSummary[] = []; for (const serverAct of serverBook.actSummaries) { const localAct = localBook.actSummaries.find(a => a.id === serverAct.id); if (!localAct || serverAct.lastUpdate > localAct.lastUpdate) { actSummariesWithChanges.push(serverAct); } } // Comparer guideline let guideLineNeedsUpdate: SyncedGuideLine | null = null; if (serverBook.guideLine && localBook.guideLine) { if (serverBook.guideLine.lastUpdate > localBook.guideLine.lastUpdate) { guideLineNeedsUpdate = serverBook.guideLine; } } else if (serverBook.guideLine && !localBook.guideLine) { guideLineNeedsUpdate = serverBook.guideLine; } // Comparer AI guideline let aiGuideLineNeedsUpdate: SyncedAIGuideLine | null = null; if (serverBook.aiGuideLine && localBook.aiGuideLine) { if (serverBook.aiGuideLine.lastUpdate > localBook.aiGuideLine.lastUpdate) { aiGuideLineNeedsUpdate = serverBook.aiGuideLine; } } else if (serverBook.aiGuideLine && !localBook.aiGuideLine) { aiGuideLineNeedsUpdate = serverBook.aiGuideLine; } // Déterminer s'il y a des changements const hasAnyChanges = bookNeedsUpdate || chaptersWithChanges.length > 0 || charactersWithChanges.length > 0 || locationsWithChanges.length > 0 || worldsWithChanges.length > 0 || incidentsWithChanges.length > 0 || plotPointsWithChanges.length > 0 || issuesWithChanges.length > 0 || actSummariesWithChanges.length > 0 || guideLineNeedsUpdate !== null || aiGuideLineNeedsUpdate !== null; return { id: serverBook.id, type: serverBook.type, title: serverBook.title, subTitle: serverBook.subTitle, bookNeedsUpdate, lastUpdate: serverBook.lastUpdate, chapters: chaptersWithChanges, characters: charactersWithChanges, locations: locationsWithChanges, worlds: worldsWithChanges, incidents: incidentsWithChanges, plotPoints: plotPointsWithChanges, issues: issuesWithChanges, actSummaries: actSummariesWithChanges, guideLine: guideLineNeedsUpdate, aiGuideLine: aiGuideLineNeedsUpdate, hasAnyChanges }; } /** * Compare tous les livres serveur vs locaux et retourne ceux qui ont des changements */ export function getBooksToSyncFromServer(serverBooks: SyncedBook[], localBooks: SyncedBook[]): BookSyncDiff[] { const booksWithChanges: BookSyncDiff[] = []; for (const serverBook of serverBooks) { const localBook = localBooks.find(b => b.id === serverBook.id); if (!localBook) { // Livre n'existe pas localement - tout le livre doit être synchronisé booksWithChanges.push({ id: serverBook.id, type: serverBook.type, title: serverBook.title, subTitle: serverBook.subTitle, bookNeedsUpdate: true, lastUpdate: serverBook.lastUpdate, chapters: serverBook.chapters, characters: serverBook.characters, locations: serverBook.locations, worlds: serverBook.worlds, incidents: serverBook.incidents, plotPoints: serverBook.plotPoints, issues: serverBook.issues, actSummaries: serverBook.actSummaries, guideLine: serverBook.guideLine, aiGuideLine: serverBook.aiGuideLine, hasAnyChanges: true }); } else { // Comparer en profondeur const diff = compareBooks(serverBook, localBook); if (diff.hasAnyChanges) { booksWithChanges.push(diff); } } } return booksWithChanges; } /** * Compare tous les livres locaux vs serveur et retourne ceux qui ont des changements locaux */ export function getBooksToSyncToServer(localBooks: SyncedBook[], serverBooks: SyncedBook[]): BookSyncDiff[] { const booksWithChanges: BookSyncDiff[] = []; for (const localBook of localBooks) { const serverBook = serverBooks.find(b => b.id === localBook.id); if (!serverBook) { // Livre n'existe pas sur le serveur - tout le livre doit être envoyé booksWithChanges.push({ id: localBook.id, type: localBook.type, title: localBook.title, subTitle: localBook.subTitle, bookNeedsUpdate: true, lastUpdate: localBook.lastUpdate, chapters: localBook.chapters, characters: localBook.characters, locations: localBook.locations, worlds: localBook.worlds, incidents: localBook.incidents, plotPoints: localBook.plotPoints, issues: localBook.issues, actSummaries: localBook.actSummaries, guideLine: localBook.guideLine, aiGuideLine: localBook.aiGuideLine, hasAnyChanges: true }); } else { // Comparer en profondeur (local vs server) const diff = compareBooks(localBook, serverBook); if (diff.hasAnyChanges) { booksWithChanges.push(diff); } } } return booksWithChanges; }