///|
/// Error type for the public read channel.
///
/// A read asks for a stored graph snapshot, so the only failures it can report
/// are *mechanism* failures — intrinsic to the read itself and unrecoverable by
/// any domain reader:
///
/// - `Cycle` — the cell transitively depends on itself.
/// - `Disposed` — the cell being read has been disposed.
///
/// Domain fallibility (a parse error, a validation failure) is *not* a read
/// error: it belongs in the value as `Result[V, E]` (see `Derived::fallible`),
/// where it is cached, change-detected, and replayed like any other value. A
/// retained compute `raise Failure` is a *defect*, not a domain error, and
/// still aborts.
///
/// Cross-runtime misuse and strict-context misuse are programmer defects and
/// abort rather than surfacing here.
///
/// See docs/design/specs/2026-05-28-honest-read-error-ownership.md.
pub enum ReadError {
  Cycle(CycleError)
  Disposed(CellId)
}

///|
/// Constructs a cycle read error. Exposed so the cells package (which detects
/// cycles) can build a `ReadError` without depending on variant-constructor
/// visibility — mirrors `CycleError::new`.
pub fn ReadError::cycle(e : CycleError) -> ReadError {
  Cycle(e)
}

///|
/// Constructs a disposed-cell read error.
pub fn ReadError::disposed(id : CellId) -> ReadError {
  Disposed(id)
}

///|
/// Renders this read error as a human-readable string. For a cycle, delegates
/// to `CycleError::format_path`; for a disposed read, names the cell.
pub fn ReadError::format_path(self : ReadError) -> String {
  match self {
    Cycle(e) => e.format_path()
    Disposed(id) => "Read on disposed cell: Cell[" + id.id.to_string() + "]"
  }
}

///|
/// Returns the cell this read error concerns: the cell at which the cycle was
/// detected, or the disposed cell that was read.
pub fn ReadError::cell(self : ReadError) -> CellId {
  match self {
    Cycle(e) => e.cell()
    Disposed(id) => id
  }
}

///|
/// Returns the cell path this read error concerns: the full dependency path
/// leading to the cycle, or the single disposed cell that was read.
pub fn ReadError::path(self : ReadError) -> Array[CellId] {
  match self {
    Cycle(e) => e.path()
    Disposed(id) => [id]
  }
}

///|
/// Returns true if this is a cycle error.
pub fn ReadError::is_cycle(self : ReadError) -> Bool {
  self is Cycle(_)
}

///|
/// Returns true if this is a disposed-cell error.
pub fn ReadError::is_disposed(self : ReadError) -> Bool {
  self is Disposed(_)
}

///|
pub impl Show for ReadError with fn output(self : ReadError, logger : &Logger) -> Unit {
  logger.write_string(self.format_path())
}