///|
/// A broken validation rule: the paths of the values it names, relative to
/// the bound section, and a detail text. An empty `at` names the section.
pub(all) struct Violation {
  at : Array[Array[DecodeStep]]
  detail : String
} derive(Eq, Debug)

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

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

///|
/// Validation rules that run after decoding. `validate` is pure: it returns
/// every broken rule and never raises. A type with no rules uses the default,
/// which returns no violations.
pub(open) trait Validate {
  validate(Self) -> Array[Violation] = _
}

///|
impl Validate with validate(_) {
  []
}

///|
/// A violation that names one field of the section for each name in
/// `fields`, or the section itself when `fields` is empty. A rule over two
/// fields names both, so the error shows the source of each value.
pub fn violation(fields : Array[String], detail : String) -> Violation {
  { at: fields.map(field => [Field(field)]), detail }
}

///|
/// The violations of the value in `field`, with each path moved below
/// `field`. A violation of the whole nested value names `field`.
pub fn nested(field : String, found : Array[Violation]) -> Array[Violation] {
  below(Field(field), found)
}

///|
fn below(step : DecodeStep, found : Array[Violation]) -> Array[Violation] {
  found.map(violation => {
    let at = if violation.at.is_empty() {
      [[step]]
    } else {
      violation.at.map(path => [step, ..path])
    }
    { ..violation, at }
  })
}

///|
/// The violations of each value, below its key.
pub impl[T : Validate] Validate for Map[String, T] with validate(self) {
  let found = []
  for key, value in self {
    found.append(below(Field(key), T::validate(value)))
  }
  found
}

///|
/// The violations of each item, below its list index.
pub impl[T : Validate] Validate for Array[T] with validate(self) {
  let found = []
  for index, item in self {
    found.append(below(Index(index), T::validate(item)))
  }
  found
}

///|
/// The violations of the value, or none for `None`.
pub impl[T : Validate] Validate for T? with validate(self) {
  match self {
    Some(value) => T::validate(value)
    None => []
  }
}

///|
/// A bound and validated value, the key path of its section, and the instance
/// name of a named option. An options value exists only after decoding and
/// validation succeed.
pub struct Options[T] {
  value : T
  key : KeyPath
  name : String?
}

///|
/// Validate `value`, the section at `key` of `config`, and give it as an
/// options value. Each violation becomes a failure with its full decode path
/// and the sources to blame, and all of them raise one `Validation`. The
/// value comes first, so `validated` ends a binding pipeline:
/// `config |> bind_or("ir", default) |> validated(config, "ir")`.
pub fn[T : Validate] validated(
  value : T,
  config : Config,
  key : String,
  name? : String,
) -> Options[T] raise ConfigError {
  let path = KeyPath::parse(key)
  let found = T::validate(value)
  if !found.is_empty() {
    raise Validation(failures=found.map(violation => {
      let paths = if violation.at.is_empty() { [[]] } else { violation.at }
      let at = paths.map(steps => {
        let path = decode_path_below(Some(path), steps)
        { path, blame: config.blame(path) }
      })
      { at, detail: violation.detail }
    }))
  }
  { value, key: path, name }
}

///|
/// Bind the section at `key` and validate it: `bind`, then `validated`.
pub fn[T : FromJson + Validate] options(
  config : Config,
  key : String,
) -> Options[T] raise ConfigError {
  let value : T = bind(config, key)
  validated(value, config, key)
}