///|
/// A value outside a limit Discord documents for an outgoing embed, message
/// component, or modal. Discord rejects the whole request with 400 Invalid
/// Form Body, so the send paths report it before any request is made.
///
/// `path` locates the value from the checked argument, such as
/// `embeds[0].fields[2].value` or `components[1].components[0].label`.
/// `custom_id` is the nearest component custom id, when there is one.
/// Lengths are UTF-16 units, like every other length check in this library.
pub(all) struct LimitViolation {
  path : String
  custom_id : String?
  reason : String
} derive(Eq, Debug)

///|
/// The violation as one line: `path: reason`.
pub fn LimitViolation::message(self : LimitViolation) -> String {
  "\{self.path}: \{self.reason}"
}

///|
/// Accumulates violations while a limit table walks a value.
priv struct LimitChecker {
  violations : Array[LimitViolation]
}

///|
fn LimitChecker::new() -> LimitChecker {
  { violations: [], }
}

///|
fn LimitChecker::report(
  self : LimitChecker,
  path : String,
  reason : String,
  custom_id? : String,
) -> Unit {
  self.violations.push({ path, custom_id, reason, })
}

///|
/// Report `text` when its UTF-16 length is outside `min..=max`. A missing
/// value is never a violation; check required fields by passing `Some`.
fn LimitChecker::text(
  self : LimitChecker,
  path : String,
  text : String?,
  min? : Int = 0,
  max~ : Int,
  custom_id? : String,
) -> Unit {
  guard text is Some(text) else { return }
  let length = text.length()
  if length < min || length > max {
    let bounds = if min > 0 { "\{min} to \{max}" } else { "at most \{max}" }
    self.report(path, "\{length} units (\{bounds})", custom_id?)
  }
}

///|
/// Report `count` when it is outside `min..=max`.
fn LimitChecker::count(
  self : LimitChecker,
  path : String,
  count : Int?,
  min~ : Int,
  max~ : Int,
  custom_id? : String,
) -> Unit {
  if count is Some(count) && (count < min || count > max) {
    self.report(path, "\{count} (\{min} to \{max})", custom_id?)
  }
}

///|
/// Report a `min_*` that exceeds its `max_*`. `path` names the `min_*` field.
fn LimitChecker::ordered(
  self : LimitChecker,
  path : String,
  min : Int?,
  max : Int?,
  custom_id? : String,
) -> Unit {
  if min is Some(min) && max is Some(max) && min > max {
    self.report(path, "\{min} exceeds the maximum \{max}", custom_id?)
  }
}