///|
using @push {type PushReactiveData, type PushEffectData}

///|
impl CellLifecycle for PushReactiveData with fn dispose_cell(self, rt, cell_id) -> Unit {
  match rt.core.cell_index[cell_id.id] {
    PushReactive(idx) => {
      for dep in self.sources {
        rt.remove_subscriber(dep, cell_id)
      }
      rt.core.cell_index[cell_id.id] = Disposed
      self.clear_slot()
      rt.push.free_reactives.push(idx)
      rt.push.node_count = rt.push.node_count - 1
    }
    _ => ()
  }
}

///|
/// Suspends the push path when the last observer is removed.
///
/// Only suspends if no other cell reads this reactive (subscribers empty).
/// A Memo or another EagerDerived in the subscriber set means this cell's
/// changed_at must keep advancing — suspension would cause stale reads.
///
/// Note: `self.sources` is NOT cleared here — it stays populated so that
/// subscriber links can be reinstated on `on_observe`. This makes
/// `gc_role = Interior`'s `gc_dependencies(self) -> self.sources` a
/// temporarily stale list while suspended. That is safe because a
/// suspended reactive with no observers is itself GC-eligible (Interior),
/// and when the sweep collects it `clear_slot` empties `sources` before
/// the slot is reused. In the observer.dispose() → on_unobserve (suspend)
/// → rt.gc() integration flow, upstream cells must not be considered
/// preserved by this stale list. Do not refactor away the "suspend
/// without clearing sources" invariant — `on_observe` depends on it.
impl CellLifecycle for PushReactiveData with fn on_unobserve(self, rt, cell_id) {
  guard self.meta.subscribers.is_empty() else { return }
  for source in self.sources {
    rt.remove_subscriber(source, cell_id)
  }
}

///|
/// Activates the push path when the first observer is added.
///
/// Recomputes with fresh tracking to establish current sources and
/// a correct cached value. Passes [] as old_sources because subscriber
/// links were removed during suspension. For fresh cells (never
/// suspended), add_subscriber is idempotent.
impl CellLifecycle for PushReactiveData with fn on_observe(self, rt, cell_id) {
  guard !(rt.core.phase is PushPropagating) else {
    abort("on_observe: cannot activate during push propagation")
  }
  guard !(rt.core.phase is InFixpoint) else {
    abort("on_observe: cannot activate during fixpoint evaluation")
  }
  rt.begin_tracking(cell_id)
  let _ = (self.compute)()
  let (new_sources, new_seen) = rt.end_tracking()
  rt.finish_tracking(cell_id, [], new_sources, new_seen)
  self.sources = new_sources
  self.level = rt.recompute_level(cell_id, new_sources)
  self.dirty = false
  if rt.has_pending_events() {
    rt.drain_pending_events_if_idle()
  }
}

///|
impl CellLifecycle for PushEffectData with fn dispose_cell(self, rt, cell_id) -> Unit {
  match rt.core.cell_index[cell_id.id] {
    PushEffect(idx) => {
      for dep in self.sources {
        rt.remove_subscriber(dep, cell_id)
      }
      rt.core.cell_index[cell_id.id] = Disposed
      self.clear_slot()
      rt.push.free_effects.push(idx)
      rt.push.node_count = rt.push.node_count - 1
    }
    _ => ()
  }
}