import Foundation struct RDEPUBChapterWindowSnapshot { let chapters: [RDEPUBRuntimeChapter] let flattenedPages: [RDEPUBTextPage] let anchorChapterIndex: Int let anchorPageOffset: Int let windowStartSpineIndex: Int static func from( chapters: [RDEPUBRuntimeChapter], anchorSpineIndex: Int ) -> RDEPUBChapterWindowSnapshot { let sortedChapters = chapters.sorted { $0.spineIndex < $1.spineIndex } let anchorIndex = sortedChapters.firstIndex { $0.spineIndex == anchorSpineIndex } ?? 0 let pageOffset = sortedChapters.prefix(anchorIndex).reduce(0) { $0 + $1.pages.count } var allPages: [RDEPUBTextPage] = [] for (chIdx, ch) in sortedChapters.enumerated() { for var page in ch.pages { page.chapterIndex = chIdx allPages.append(page) } } let windowStartSpineIndex = sortedChapters.first?.spineIndex ?? anchorSpineIndex return RDEPUBChapterWindowSnapshot( chapters: sortedChapters, flattenedPages: allPages, anchorChapterIndex: anchorIndex, anchorPageOffset: pageOffset, windowStartSpineIndex: windowStartSpineIndex ) } func chapterForPage(flattenedPageIndex: Int) -> RDEPUBRuntimeChapter? { var offset = 0 for ch in chapters { if flattenedPageIndex < offset + ch.pages.count { return ch } offset += ch.pages.count } return nil } func spineIndexForPage(flattenedPageIndex: Int) -> Int? { return chapterForPage(flattenedPageIndex: flattenedPageIndex)?.spineIndex } var pageCount: Int { flattenedPages.count } }