///|
pub(all) enum Quantity {
  Time
  Capacitance
  Voltage
} derive(Eq, Debug)

///|
pub(all) struct Units {
  time_seconds : Double
  capacitance_farads : Double
  voltage_volts : Double?
} derive(Eq, Debug)

///|
pub fn finite_number(
  text : String,
  position~ : Position = no_position(),
) -> Double raise LibertyError {
  let clean = text.trim()
  if clean.is_empty() {
    raise failure("InvalidNumber", "empty numeric value", position)
  }
  let value = @string.parse_double(clean) catch {
    _ =>
      raise failure("InvalidNumber", "invalid numeric value: " + text, position)
  }
  if value.is_nan() || value.is_inf() {
    raise failure("NonFiniteNumber", "finite numeric value required", position)
  }
  value
}

///|
pub fn parse_unit(
  text : String,
  quantity : Quantity,
) -> Double raise LibertyError {
  let value = text.trim().to_owned()
  let suffixes : Array[(String, Double)] = match quantity {
    Time =>
      [
        ("ms", 1.0e-3),
        ("us", 1.0e-6),
        ("ns", 1.0e-9),
        ("ps", 1.0e-12),
        ("fs", 1.0e-15),
        ("s", 1.0),
      ]
    Capacitance =>
      [
        ("uF", 1.0e-6),
        ("nF", 1.0e-9),
        ("pF", 1.0e-12),
        ("fF", 1.0e-15),
        ("uf", 1.0e-6),
        ("nf", 1.0e-9),
        ("pf", 1.0e-12),
        ("ff", 1.0e-15),
        ("F", 1.0),
        ("f", 1.0),
      ]
    Voltage =>
      [
        ("mV", 1.0e-3),
        ("uV", 1.0e-6),
        ("mv", 1.0e-3),
        ("uv", 1.0e-6),
        ("V", 1.0),
        ("v", 1.0),
      ]
  }
  for entry in suffixes {
    if value.has_suffix(entry.0) {
      let prefix = value[0:value.length() - entry.0.length()].to_owned()
      let factor = finite_number(prefix)
      let scaled = factor * entry.1
      if factor <= 0.0 || scaled <= 0.0 || scaled.is_nan() || scaled.is_inf() {
        raise failure(
          "InvalidUnit",
          "unit scale must be positive and finite",
          no_position(),
        )
      }
      return scaled
    }
  }
  raise failure("UnsupportedUnit", "unsupported unit: " + text, no_position())
}

///|
/// Validate public, possibly caller-constructed unit scales before arithmetic.
pub fn validate_units(units : Units) -> Unit raise LibertyError {
  let scales = [units.time_seconds, units.capacitance_farads]
  if units.voltage_volts is Some(voltage) {
    scales.push(voltage)
  }
  if scales.any(scale => scale <= 0.0 || scale.is_nan() || scale.is_inf()) {
    raise failure(
      "InvalidUnit",
      "all declared SI scales must be positive and finite",
      no_position(),
    )
  }
}

///|
pub fn read_units(root : Group) -> Units raise LibertyError {
  let time = match root.text("time_unit") {
    Some(text) => parse_unit(text, Time)
    None =>
      raise failure(
        "MissingUnit",
        "time_unit is required for SI timing queries",
        root.position,
      )
  }
  let capacitance = match root.complex("capacitive_load_unit") {
    Some(args) if args.length() == 2 &&
      args[0].length() == 1 &&
      args[1].length() == 1 =>
      parse_unit(atom_text(args[0][0]) + atom_text(args[1][0]), Capacitance)
    _ =>
      raise failure(
        "MissingUnit",
        "capacitive_load_unit(number,unit) is required",
        root.position,
      )
  }
  let voltage = match root.text("voltage_unit") {
    Some(text) => Some(parse_unit(text, Voltage))
    None => None
  }
  {
    time_seconds: time,
    capacitance_farads: capacitance,
    voltage_volts: voltage,
  }
}

///|
pub fn number_list(
  atoms : Array[Atom],
  position~ : Position = no_position(),
) -> Array[Double] raise LibertyError {
  if atoms.length() != 1 {
    raise failure(
      "ExpectedNumbers", "expected one numeric atom or a quoted comma-separated row",
      position,
    )
  }
  let raw = atom_text(atoms[0])
  let numbers = []
  for part in raw.split(",") {
    numbers.push(finite_number(part.to_owned(), position~))
  }
  numbers
}

///|
fn vector(group : Group, name : String) -> Array[Double]? raise LibertyError {
  match group.complex(name) {
    None => None
    Some(arguments) => {
      let numbers = []
      for argument in arguments {
        numbers.append(number_list(argument, position=group.position))
      }
      Some(numbers)
    }
  }
}