///|
/// The rule of an artifact digest that a text breaks.
pub enum DigestRule {
  /// The text has no `algorithm:` prefix.
  MissingAlgorithm
  /// The algorithm is not `sha256`.
  UnsupportedAlgorithm(String)
  /// The value is not hex.
  NotHex
  /// The value does not have 32 bytes.
  WrongLength(Int)
} derive(Eq, Debug)

///|
pub extend DigestRule with Eq::{equal, not_equal}

///|
pub extend DigestRule with @debug.Debug::{to_repr}

///|
/// A text that is not a valid artifact digest.
pub suberror DigestError {
  InvalidDigest(text~ : String, rule~ : DigestRule)
} derive(Debug)

///|
pub extend DigestError with @debug.Debug::{to_repr}

///|
/// Text for people that names the input and the rule.
pub fn DigestError::message(self : DigestError) -> String {
  let InvalidDigest(text~, rule~) = self
  let reason = match rule {
    MissingAlgorithm => "use the form sha256:"
    UnsupportedAlgorithm(name) => "the algorithm \{name.escape()} is not supported; use sha256"
    NotHex => "the value is not hex"
    WrongLength(size) => "a SHA-256 digest has 32 bytes, not \{size}"
  }
  "Invalid artifact digest \{text.escape()}: \{reason}"
}

///|
/// The SHA-256 digest of an artifact.
pub struct ArtifactDigest {
  priv bytes : Bytes
} derive(Eq, Debug)

///|
pub extend ArtifactDigest with Eq::{equal, not_equal}

///|
pub extend ArtifactDigest with @debug.Debug::{to_repr}

///|
/// Parse a digest such as `sha256:03d190…32bd`. Hex digits can be in either case.
pub fn ArtifactDigest::parse(text : String) -> ArtifactDigest raise DigestError {
  guard text.find(":") is Some(colon) else {
    raise InvalidDigest(text~, rule=MissingAlgorithm)
  }
  let algorithm = text[:colon].to_owned()
  guard algorithm == "sha256" else {
    raise InvalidDigest(text~, rule=UnsupportedAlgorithm(algorithm))
  }
  let bytes = @encoding.decode_hex(text[colon + 1:]) catch {
    _ => raise InvalidDigest(text~, rule=NotHex)
  }
  guard bytes.length() == 32 else {
    raise InvalidDigest(text~, rule=WrongLength(bytes.length()))
  }
  { bytes, }
}

///|
/// The digest of the artifact bytes.
pub fn ArtifactDigest::of_bytes(artifact : BytesView) -> ArtifactDigest {
  { bytes: @signature.sha256(artifact) }
}

///|
/// The 32 digest bytes.
pub fn ArtifactDigest::to_bytes(self : ArtifactDigest) -> Bytes {
  self.bytes
}

///|
/// The digest as `sha256:`, which `ArtifactDigest::parse` reads back.
pub impl Show for ArtifactDigest with output(self, logger) {
  logger.write_string("sha256:")
  logger.write_string(@encoding.encode_hex(self.bytes))
}

///|
pub extend ArtifactDigest with Show::{output, to_string}