///|
fn signed_nibble(value : Int) -> Int {
  let nibble = value & 15
  if nibble >= 8 {
    nibble - 16
  } else {
    nibble
  }
}

///|
/// Unit dimensions are signed nibble exponents, not converted physical values.
pub(all) struct UnitDimensions {
  raw : Int64
  system : Int
  length : Int
  mass : Int
  time : Int
  temperature : Int
  current : Int
  luminous_intensity : Int
  reserved : Int
} derive(Debug, Eq)

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

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

///|
pub fn decode_unit(raw : Int64) -> Result[UnitDimensions, Diagnostic] {
  if raw < 0 || raw > 4294967295L {
    return Err(
      diagnostic(0, "unit_range", "Unit must fit an unsigned 32-bit item"),
    )
  }
  Ok({
    raw,
    system: (raw & 15L).to_int(),
    length: signed_nibble((raw >> 4).to_int()),
    mass: signed_nibble((raw >> 8).to_int()),
    time: signed_nibble((raw >> 12).to_int()),
    temperature: signed_nibble((raw >> 16).to_int()),
    current: signed_nibble((raw >> 20).to_int()),
    luminous_intensity: signed_nibble((raw >> 24).to_int()),
    reserved: ((raw >> 28) & 15L).to_int(),
  })
}

///|
/// HID defaults to the logical range when either physical endpoint is absent,
/// or both declared physical endpoints are zero. Declared metadata stays intact.
pub fn effective_physical_range(field : Field) -> (Int64, Int64) {
  match (field.physical_min, field.physical_max) {
    (Some(minimum), Some(maximum)) if minimum != 0 || maximum != 0 =>
      (minimum, maximum)
    _ => (field.logical_min, field.logical_max)
  }
}