///| A hybrid logical timestamp. `physical` is supplied by the caller so the

///|
/// algorithm remains deterministic in tests, simulations, and WASM runtimes.
pub(all) struct HlcTimestamp {
  physical : Int
  logical : Int
  node : String
} derive(Eq, Debug)

///| Create a timestamp. Negative physical time and logical counters are not

///| valid HLC values, so this returns an error instead of silently accepting

///|
/// malformed decoded data.
pub fn HlcTimestamp::new(
  physical : Int,
  logical : Int,
  node : String,
) -> Result[HlcTimestamp, TimestampError] {
  if physical < 0 {
    Err(NegativePhysicalTime(physical))
  } else if logical < 0 {
    Err(NegativeLogicalCounter(logical))
  } else if node.length() == 0 {
    Err(EmptyNodeId)
  } else {
    Ok({ physical, logical, node })
  }
}

///| Return a stable human-readable representation suitable for traces. This is

///| deliberately not a wire format: consumers should choose their own storage

///|
/// format and validate it at the boundary.
pub fn HlcTimestamp::to_string(self : HlcTimestamp) -> String {
  self.physical.to_string() + ":" + self.logical.to_string() + ":" + self.node
}

///| Total-order two timestamps by physical time, logical counter, then node.

///|
/// A negative value means `self` comes before `other`.
pub fn HlcTimestamp::compare(self : HlcTimestamp, other : HlcTimestamp) -> Int {
  if self.physical < other.physical {
    -1
  } else if self.physical > other.physical {
    1
  } else if self.logical < other.logical {
    -1
  } else if self.logical > other.logical {
    1
  } else if self.node < other.node {
    -1
  } else if self.node > other.node {
    1
  } else {
    0
  }
}

///|
/// True when `self` is no later than `other` in HLC total order.
pub fn HlcTimestamp::precedes_or_equals(
  self : HlcTimestamp,
  other : HlcTimestamp,
) -> Bool {
  self.compare(other) <= 0
}

///|
/// Timestamp construction and decoding errors.
pub(all) enum TimestampError {
  NegativePhysicalTime(Int)
  NegativeLogicalCounter(Int)
  EmptyNodeId
} derive(Eq, Debug)