export interface SyncedBook { id: string; type: string; title: string; subTitle: string | null; lastUpdate: number; chapters: SyncedChapter[]; characters: SyncedCharacter[]; locations: SyncedLocation[]; worlds: SyncedWorld[]; incidents: SyncedIncident[]; plotPoints: SyncedPlotPoint[]; issues: SyncedIssue[]; actSummaries: SyncedActSummary[]; guideLine: SyncedGuideLine | null; aiGuideLine: SyncedAIGuideLine | null; } export interface SyncedChapter { id: string; name: string; lastUpdate: number; contents: SyncedChapterContent[]; info: SyncedChapterInfo | null; } export interface SyncedChapterContent { id: string; lastUpdate: number; } export interface SyncedChapterInfo { id: string; lastUpdate: number; } export interface SyncedCharacter { id: string; name: string; lastUpdate: number; attributes: SyncedCharacterAttribute[]; } export interface SyncedCharacterAttribute { id: string; name: string; lastUpdate: number; } export interface SyncedLocation { id: string; name: string; lastUpdate: number; elements: SyncedLocationElement[]; } export interface SyncedLocationElement { id: string; name: string; lastUpdate: number; subElements: SyncedLocationSubElement[]; } export interface SyncedLocationSubElement { id: string; name: string; lastUpdate: number; } export interface SyncedWorld { id: string; name: string; lastUpdate: number; elements: SyncedWorldElement[]; } export interface SyncedWorldElement { id: string; name: string; lastUpdate: number; } export interface SyncedIncident { id: string; name: string; lastUpdate: number; } export interface SyncedPlotPoint { id: string; name: string; lastUpdate: number; } export interface SyncedIssue { id: string; name: string; lastUpdate: number; } export interface SyncedActSummary { id: string; lastUpdate: number; } export interface SyncedGuideLine { lastUpdate: number; } export interface SyncedAIGuideLine { lastUpdate: number; } export interface BookSyncCompare { id: string; chapters: string[]; chapterContents: string[]; chapterInfos: string[]; characters: string[]; characterAttributes: string[]; locations: string[]; locationElements: string[]; locationSubElements: string[]; worlds: string[]; worldElements: string[]; incidents: string[]; plotPoints: string[]; issues: string[]; actSummaries: string[]; guideLine: boolean; aiGuideLine: boolean; } export function compareBookSyncs(newerBook: SyncedBook, olderBook: SyncedBook): BookSyncCompare | null { const changedChapterIds: string[] = []; const changedChapterContentIds: string[] = []; const changedChapterInfoIds: string[] = []; const changedCharacterIds: string[] = []; const changedCharacterAttributeIds: string[] = []; const changedLocationIds: string[] = []; const changedLocationElementIds: string[] = []; const changedLocationSubElementIds: string[] = []; const changedWorldIds: string[] = []; const changedWorldElementIds: string[] = []; const changedIncidentIds: string[] = []; const changedPlotPointIds: string[] = []; const changedIssueIds: string[] = []; const changedActSummaryIds: string[] = []; let guideLineChanged: boolean = false; let aiGuideLineChanged: boolean = false; newerBook.chapters.forEach((newerChapter: SyncedChapter): void => { const olderChapter: SyncedChapter | undefined = olderBook.chapters.find((chapter: SyncedChapter): boolean => chapter.id === newerChapter.id); if (!olderChapter) { changedChapterIds.push(newerChapter.id); newerChapter.contents.forEach((content: SyncedChapterContent): void => { changedChapterContentIds.push(content.id); }); if (newerChapter.info) { changedChapterInfoIds.push(newerChapter.info.id); } } else if (newerChapter.lastUpdate > olderChapter.lastUpdate) { changedChapterIds.push(newerChapter.id); } else { newerChapter.contents.forEach((newerContent: SyncedChapterContent): void => { const olderContent: SyncedChapterContent | undefined = olderChapter.contents.find((content: SyncedChapterContent): boolean => content.id === newerContent.id); if (!olderContent || newerContent.lastUpdate > olderContent.lastUpdate) { changedChapterContentIds.push(newerContent.id); } }); if (newerChapter.info && olderChapter.info) { if (newerChapter.info.lastUpdate > olderChapter.info.lastUpdate) { changedChapterInfoIds.push(newerChapter.info.id); } } else if (newerChapter.info && !olderChapter.info) { changedChapterInfoIds.push(newerChapter.info.id); } } }); newerBook.characters.forEach((newerCharacter: SyncedCharacter): void => { const olderCharacter: SyncedCharacter | undefined = olderBook.characters.find((character: SyncedCharacter): boolean => character.id === newerCharacter.id); if (!olderCharacter) { changedCharacterIds.push(newerCharacter.id); newerCharacter.attributes.forEach((attribute: SyncedCharacterAttribute): void => { changedCharacterAttributeIds.push(attribute.id); }); } else if (newerCharacter.lastUpdate > olderCharacter.lastUpdate) { changedCharacterIds.push(newerCharacter.id); } else { newerCharacter.attributes.forEach((newerAttribute: SyncedCharacterAttribute): void => { const olderAttribute: SyncedCharacterAttribute | undefined = olderCharacter.attributes.find((attribute: SyncedCharacterAttribute): boolean => attribute.id === newerAttribute.id); if (!olderAttribute || newerAttribute.lastUpdate > olderAttribute.lastUpdate) { changedCharacterAttributeIds.push(newerAttribute.id); } }); } }); newerBook.locations.forEach((newerLocation: SyncedLocation): void => { const olderLocation: SyncedLocation | undefined = olderBook.locations.find((location: SyncedLocation): boolean => location.id === newerLocation.id); if (!olderLocation) { changedLocationIds.push(newerLocation.id); newerLocation.elements.forEach((element: SyncedLocationElement): void => { changedLocationElementIds.push(element.id); element.subElements.forEach((subElement: SyncedLocationSubElement): void => { changedLocationSubElementIds.push(subElement.id); }); }); } else if (newerLocation.lastUpdate > olderLocation.lastUpdate) { changedLocationIds.push(newerLocation.id); } else { newerLocation.elements.forEach((newerElement: SyncedLocationElement): void => { const olderElement: SyncedLocationElement | undefined = olderLocation.elements.find((element: SyncedLocationElement): boolean => element.id === newerElement.id); if (!olderElement) { changedLocationElementIds.push(newerElement.id); newerElement.subElements.forEach((subElement: SyncedLocationSubElement): void => { changedLocationSubElementIds.push(subElement.id); }); } else if (newerElement.lastUpdate > olderElement.lastUpdate) { changedLocationElementIds.push(newerElement.id); } else { newerElement.subElements.forEach((newerSubElement: SyncedLocationSubElement): void => { const olderSubElement: SyncedLocationSubElement | undefined = olderElement.subElements.find((subElement: SyncedLocationSubElement): boolean => subElement.id === newerSubElement.id); if (!olderSubElement || newerSubElement.lastUpdate > olderSubElement.lastUpdate) { changedLocationSubElementIds.push(newerSubElement.id); } }); } }); } }); newerBook.worlds.forEach((newerWorld: SyncedWorld): void => { const olderWorld: SyncedWorld | undefined = olderBook.worlds.find((world: SyncedWorld): boolean => world.id === newerWorld.id); if (!olderWorld) { changedWorldIds.push(newerWorld.id); newerWorld.elements.forEach((element: SyncedWorldElement): void => { changedWorldElementIds.push(element.id); }); } else if (newerWorld.lastUpdate > olderWorld.lastUpdate) { changedWorldIds.push(newerWorld.id); } else { newerWorld.elements.forEach((newerElement: SyncedWorldElement): void => { const olderElement: SyncedWorldElement | undefined = olderWorld.elements.find((element: SyncedWorldElement): boolean => element.id === newerElement.id); if (!olderElement || newerElement.lastUpdate > olderElement.lastUpdate) { changedWorldElementIds.push(newerElement.id); } }); } }); newerBook.incidents.forEach((newerIncident: SyncedIncident): void => { const olderIncident: SyncedIncident | undefined = olderBook.incidents.find((incident: SyncedIncident): boolean => incident.id === newerIncident.id); if (!olderIncident || newerIncident.lastUpdate > olderIncident.lastUpdate) { changedIncidentIds.push(newerIncident.id); } }); newerBook.plotPoints.forEach((newerPlotPoint: SyncedPlotPoint): void => { const olderPlotPoint: SyncedPlotPoint | undefined = olderBook.plotPoints.find((plotPoint: SyncedPlotPoint): boolean => plotPoint.id === newerPlotPoint.id); if (!olderPlotPoint || newerPlotPoint.lastUpdate > olderPlotPoint.lastUpdate) { changedPlotPointIds.push(newerPlotPoint.id); } }); newerBook.issues.forEach((newerIssue: SyncedIssue): void => { const olderIssue: SyncedIssue | undefined = olderBook.issues.find((issue: SyncedIssue): boolean => issue.id === newerIssue.id); if (!olderIssue || newerIssue.lastUpdate > olderIssue.lastUpdate) { changedIssueIds.push(newerIssue.id); } }); newerBook.actSummaries.forEach((newerActSummary: SyncedActSummary): void => { const olderActSummary: SyncedActSummary | undefined = olderBook.actSummaries.find((actSummary: SyncedActSummary): boolean => actSummary.id === newerActSummary.id); if (!olderActSummary || newerActSummary.lastUpdate > olderActSummary.lastUpdate) { changedActSummaryIds.push(newerActSummary.id); } }); if (newerBook.guideLine && olderBook.guideLine) { guideLineChanged = newerBook.guideLine.lastUpdate > olderBook.guideLine.lastUpdate; } else if (newerBook.guideLine && !olderBook.guideLine) { guideLineChanged = true; } if (newerBook.aiGuideLine && olderBook.aiGuideLine) { aiGuideLineChanged = newerBook.aiGuideLine.lastUpdate > olderBook.aiGuideLine.lastUpdate; } else if (newerBook.aiGuideLine && !olderBook.aiGuideLine) { aiGuideLineChanged = true; } const hasChanges: boolean = changedChapterIds.length > 0 || changedChapterContentIds.length > 0 || changedChapterInfoIds.length > 0 || changedCharacterIds.length > 0 || changedCharacterAttributeIds.length > 0 || changedLocationIds.length > 0 || changedLocationElementIds.length > 0 || changedLocationSubElementIds.length > 0 || changedWorldIds.length > 0 || changedWorldElementIds.length > 0 || changedIncidentIds.length > 0 || changedPlotPointIds.length > 0 || changedIssueIds.length > 0 || changedActSummaryIds.length > 0 || guideLineChanged || aiGuideLineChanged; if (!hasChanges) { return null; } return { id: newerBook.id, chapters: changedChapterIds, chapterContents: changedChapterContentIds, chapterInfos: changedChapterInfoIds, characters: changedCharacterIds, characterAttributes: changedCharacterAttributeIds, locations: changedLocationIds, locationElements: changedLocationElementIds, locationSubElements: changedLocationSubElementIds, worlds: changedWorldIds, worldElements: changedWorldElementIds, incidents: changedIncidentIds, plotPoints: changedPlotPointIds, issues: changedIssueIds, actSummaries: changedActSummaryIds, guideLine: guideLineChanged, aiGuideLine: aiGuideLineChanged }; }