<commit_before>import CoreData

extension PlanDay {
  private static var context: UInt8 = 0

  override public class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
    var keyPaths = super.keyPathsForValuesAffectingValue(forKey: key)

    switch key {
    case #keyPath(name):
      keyPaths.insert(#keyPath(order))
    case #keyPath(weekName):
      keyPaths.insert(#keyPath(week))
    default:
      break
    }

    return keyPaths
  }

  convenience init(scriptures: ScriptureCollection, order: Int, week: Int, insertInto context: NSManagedObjectContext?) {
    self.init(entity: type(of: self).entity(), insertInto: context)

    self.summary = scriptures.summary
    self.order = Int16(order)
    self.week = Int16(week)

    for (offset, scripture) in scriptures.chapters.enumerated() {
      let chapter = PlanChapter(scripture: scripture, order: offset + 1, insertInto: context)
      addToChapters(chapter)
    }
  }

  public override func awakeFromFetch() {
    super.awakeFromFetch()
    addObservers()
  }

  public override func awakeFromInsert() {
    super.awakeFromInsert()
    addObservers()
  }

  public override func willTurnIntoFault() {
    super.willTurnIntoFault()
    removeObservers()
  }

  private func addObservers() {
    chapters?.forEach { chapter in
      (chapter as AnyObject).addObserver(self, forKeyPath: #keyPath(PlanChapter.isRead), options: [.initial, .new], context: &PlanDay.context)
    }
  }

  private func removeObservers() {
    chapters?.forEach { chapter in
      (chapter as AnyObject).removeObserver(self, forKeyPath: #keyPath(PlanChapter.isRead), context: &PlanDay.context)
    }
  }

  public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    guard context == &PlanDay.context,
      let chapter = object,
      chapters?.contains(chapter) == true,
      keyPath == #keyPath(PlanChapter.isRead)
      else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        return
      }

    chaptersRead = value(forKeyPath: "chapters.@sum.isRead") as! Int16
  }

  var chapterCount: Int {
    return chapters!.count
  }

  var formattedSummary: String {
    return summary!
      .replacingOccurrences(of: "-", with: "–")
  }

  var isComplete: Bool {
    return percentageRead == 1
  }

  var isInProgress: Bool {
    let percentage = percentageRead
    return 0 < percentage && percentage < 1
  }

  var name: String {
    return String(
      format: NSLocalizedString("Day %d", comment: "Day description format"),
      order
    )<commit_msg>Fix read state observation crash<commit_after>import CoreData

extension PlanDay {
  convenience init(scriptures: ScriptureCollection, order: Int, week: Int, insertInto context: NSManagedObjectContext?) {
    self.init(entity: type(of: self).entity(), insertInto: context)

    self.summary = scriptures.summary
    self.order = Int16(order)
    self.week = Int16(week)

    for (offset, scripture) in scriptures.chapters.enumerated() {
      let chapter = PlanChapter(scripture: scripture, order: offset + 1, insertInto: context)
      addToChapters(chapter)
    }
  }

  public override func awakeFromFetch() {
    super.awakeFromFetch()
    updateChaptersRead()
    addObservers()
  }

  public override func awakeFromInsert() {
    super.awakeFromInsert()
    updateChaptersRead()
    addObservers()
  }

  private func addObservers() {
    NotificationCenter.default.addObserver(
      self,
      selector: #selector(objectsDidChange(_:)),
      name: .NSManagedObjectContextObjectsDidChange,
      object: managedObjectContext!
    )
  }

  @objc private func objectsDidChange(_ notification: Notification) {
    guard let refreshed = notification.userInfo![NSRefreshedObjectsKey] as! Set<AnyHashable>?,
      let chapters = chapters as! Set<AnyHashable>?,
      !chapters.intersection(refreshed).isEmpty
      else { return }

    updateChaptersRead()
  }

  private func updateChaptersRead() {
    chaptersRead = value(forKeyPath: "chapters.@sum.isRead") as! Int16
  }

  var chapterCount: Int {
    return chapters!.count
  }

  var formattedSummary: String {
    return summary!
      .replacingOccurrences(of: "-", with: "–")
  }

  var isComplete: Bool {
    return percentageRead == 1
  }

  var isInProgress: Bool {
    let percentage = percentageRead
    return 0 < percentage && percentage < 1
  }

  var name: String {
    return String(
      format: NSLocalizedString("Day %d", comment: "Day description format"),
      order
    )
{'fraction_non_alphanumeric': 0.10473923393204934, 'fraction_numerical': 0.004760874269638607, 'mean_word_length': 3.522504892367906, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1080407', 'n_tokens_mistral': 1428, 'n_tokens_neox': 1362, 'n_words': 340}