import Foundation final class RDEPUBChapterDataCache { private var storage: [Int: RDEPUBRuntimeChapter] = [:] private var accessOrder: [Int] = [] // H-05: LRU tracking for eviction private let maxEntryCount: Int private let lock = NSLock() init(maxEntryCount: Int = 30) { self.maxEntryCount = maxEntryCount } subscript(_ spineIndex: Int) -> RDEPUBRuntimeChapter? { get { lock.lock() defer { lock.unlock() } guard let chapter = storage[spineIndex] else { return nil } touchLocked(spineIndex) return chapter } set { lock.lock() defer { lock.unlock() } if let newValue { storage[spineIndex] = newValue touchLocked(spineIndex) // Evict oldest entries if over limit evictIfNeededLocked() } else { storage.removeValue(forKey: spineIndex) accessOrder.removeAll { $0 == spineIndex } } } } var storedSpineIndices: [Int] { lock.lock() defer { lock.unlock() } return Array(storage.keys) } func remove(spineIndex: Int) { lock.lock() defer { lock.unlock() } storage.removeValue(forKey: spineIndex) accessOrder.removeAll { $0 == spineIndex } } func removeAll() { lock.lock() defer { lock.unlock() } storage.removeAll() accessOrder.removeAll() } /// H-05: Evict least recently used entries when cache exceeds maxEntryCount. /// Must be called while holding lock. private func evictIfNeededLocked() { while storage.count > maxEntryCount, let oldest = accessOrder.first { storage.removeValue(forKey: oldest) accessOrder.removeFirst() } } /// Marks an entry as most recently used. /// Must be called while holding lock. private func touchLocked(_ spineIndex: Int) { accessOrder.removeAll { $0 == spineIndex } accessOrder.append(spineIndex) } }