//! Staged-delivery measurement (`extensions/staged-measurement.md`): the deterministic
//! figures of a staged title, computed through the reference sink.
//!
//! - [`title`]: a measured title, read from a fixture directory or from a map of files.
//! - [`decode`]: its bases decoded by `staged::decode_base_pinned`, the sink's own base
//!   decode, under the title's cost limit, and its layers applied by
//!   `staged::StageSink` under its chain budget, each unit's output at each stage handed
//!   over as it is reached.
//! - [`run`]: the evaluation — counted bytes per object, stage and prefix (§3), quality
//!   per presentation and per prefix (§4), first presentable (§5.1), the layering
//!   overhead's rate–quality points (§6), deduplication (§9) and the output tree
//!   (Appendix C) — and its check against the fixture tool's own reconstruction.
//! - [`sim`]: the staged delivery simulator (§10, Appendix B), its stall bound and the
//!   check of a plan against it.
//! - `session` (with `receipt_claims`): the `reconstruct-session` claim a sink would seal
//!   over a title's evaluation (§8.7, jwp-receipts §2.4.12), its output tree the title's.
//!
//! Everything here is integer and reads no clock and no file outside [`title::Title::load`],
//! so it builds for wasm32 and every machine computes the same figures.

pub mod decode;
pub mod run;
#[cfg(feature = "receipt_claims")]
pub mod session;
pub mod sim;
pub mod title;

/// The `format` of a fixture's `expect.json`: the reconstruction digests the fixture
/// tool computed from its own encoder.
pub const EXPECT_FORMAT: &str = "wai-measure-expect/1";

/// The `format` of a corpus's `goldens.json`.
pub const GOLDENS_FORMAT: &str = "wai-measure-goldens/1";

/// Why a title could not be read or evaluated.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MeasureError {
    /// A file the title needs is not among those given: its path below `wai/`.
    Missing(String),
    /// The title, an object or a figure is not what the method requires.
    Invalid(String),
    /// A base or a layer is refused under the title's limits, with the refusal's code:
    /// the base decode's (`staged::BaseError::code`: `cost_over_limit`,
    /// `missing_capability`, a pin refusal, …), or the sink's (`chain-over-limit`, a
    /// layer's §4.2 reason).
    Refused { at: String, code: String, detail: String },
}

impl MeasureError {
    /// The refusal's code, for a refusal.
    pub fn code(&self) -> Option<&str> {
        match self {
            Self::Refused { code, .. } => Some(code),
            _ => None,
        }
    }
}

impl std::fmt::Display for MeasureError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Missing(p) => write!(f, "{p}: not given"),
            Self::Invalid(s) => f.write_str(s),
            Self::Refused { at, code, detail } => write!(f, "{at}: refused, {code}: {detail}"),
        }
    }
}

impl std::error::Error for MeasureError {}
