///|
fn bound_number(
  expr : Expr,
  kind : Kind,
  values : Map[String, Value],
  linux612~ : Bool = false,
) -> Int64 raise ConfigError {
  if linux612 {
    let (raw, declared_kind, _) = linux_operand(expr, values)
    let actual_kind = match declared_kind {
      Some(Integer) => Integer
      Some(Hex) => Hex
      _ => kind
    }
    return linux_range_number(raw, actual_kind)
  }
  match expr.typed_value(kind, values) {
    Number(n) => n
    Unset => 0L
    Text(s) => if s.is_empty() { 0L } else { parse_number(s) }
    _ => raise Invalid("numeric range bound has logical type")
  }
}

///|
fn active_range(
  schema : Schema,
  d : Definition,
  values : Map[String, Value],
) -> (Int64, Int64)? raise ConfigError {
  let modules = schema.modules_enabled(values)
  for (low, high, cond) in d.ranges {
    if cond
      .condition(values, modules, linux612=schema.linux612)
      .meet(d.depends.condition(values, modules, linux612=schema.linux612)) !=
      N {
      let lo = bound_number(
        low,
        schema.kind(d.name),
        values,
        linux612=schema.linux612,
      )
      let hi = bound_number(
        high,
        schema.kind(d.name),
        values,
        linux612=schema.linux612,
      )
      if lo > hi && !schema.linux612 {
        raise Invalid("inverted active range: " + d.name)
      }
      return Some((lo, hi))
    }
  }
  None
}

///|
fn request_in_range(
  schema : Schema,
  d : Definition,
  value : Value,
  values : Map[String, Value],
) -> Bool raise ConfigError {
  match (value, active_range(schema, d, values)) {
    (Number(n), Some((lo, hi))) => n >= lo && n <= hi
    _ => true
  }
}

///|
fn clamp_default(
  schema : Schema,
  d : Definition,
  value : Value,
  values : Map[String, Value],
) -> Value raise ConfigError {
  if schema.linux612 {
    if schema.kind(d.name) != Integer && schema.kind(d.name) != Hex {
      return value
    }
    let modules = schema.modules_enabled(values)
    for (low, high, cond) in d.ranges {
      if cond
        .condition(values, modules, linux612=true)
        .meet(d.depends.condition(values, modules, linux612=true)) ==
        N {
        continue
      }
      let lo = bound_number(low, schema.kind(d.name), values, linux612=true)
      let hi = bound_number(high, schema.kind(d.name), values, linux612=true)
      let n = linux_range_number(value.text(), schema.kind(d.name))
      let endpoint = if n < lo {
        Some(low)
      } else if n > hi {
        Some(high)
      } else {
        None
      }
      return match endpoint {
        Some(e) => e.typed_value(schema.kind(d.name), values, linux612=true)
        None => value
      }
    }
    return value
  }
  match active_range(schema, d, values) {
    None => value
    Some((lo, hi)) => {
      let n = match value {
        Number(n) => n
        RawNumber(raw, k) => linux_range_number(raw, k)
        _ => 0L
      }
      if n < lo {
        if schema.linux612 {
          RawNumber(
            if schema.kind(d.name) == Hex {
              hex_text(lo)
            } else {
              lo.to_string()
            },
            schema.kind(d.name),
          )
        } else {
          Number(lo)
        }
      } else if n > hi {
        if schema.linux612 {
          RawNumber(
            if schema.kind(d.name) == Hex {
              hex_text(hi)
            } else {
              hi.to_string()
            },
            schema.kind(d.name),
          )
        } else {
          Number(hi)
        }
      } else {
        value
      }
    }
  }
}