///|
pub suberror ModelError {
  Invalid(String)
  Limit(String)
} derive(Debug)

///|
pub extend ModelError with @moonbitlang/core/debug.Debug::{to_repr}

///|
/// An immutable field definition with indexed, on-demand mutation generation.
pub struct Field {
  value : Bytes
  count : Int
  candidate : (Int) -> Bytes
  candidate_length : (Int) -> Int64
}

///|
/// Append user-supplied fuzz values after the built-in candidates with
/// continuous mutation indices, following boofuzz/fuzzable.py get_mutations
/// at 518c139 (fuzz_values chain after mutations(); index keeps incrementing).
/// Only applied when the field is fuzzable; disabled fields stay at zero.
fn Field::with_fuzz_values(
  base : Field,
  extra : Array[Bytes],
  fuzzable : Bool,
) -> Field {
  if !fuzzable || extra.is_empty() {
    return base
  }
  let saved = extra.copy()
  let base_count = base.count
  {
    value: base.value,
    count: base_count + saved.length(),
    candidate: fn(i) {
      if i < base_count {
        (base.candidate)(i)
      } else {
        saved[i - base_count]
      }
    },
    candidate_length: fn(i) {
      if i < base_count {
        (base.candidate_length)(i)
      } else {
        saved[i - base_count].length().to_int64()
      }
    },
  }
}

///|
pub fn Field::simple(
  value : Bytes,
  values : Array[Bytes],
  fuzzable? : Bool = true,
  fuzz_values? : Array[Bytes] = [],
) -> Field {
  let saved = values.copy()
  for item in fuzz_values {
    saved.push(item)
  }
  {
    value,
    count: if fuzzable {
      saved.length()
    } else {
      0
    },
    candidate: fn(i) { saved[i] },
    candidate_length: i => saved[i].length().to_int64(),
  }
}

///|
/// Group removes only the first occurrence of its default from candidates.
pub fn Field::group(
  values : Array[Bytes],
  default_value? : Bytes,
  fuzzable? : Bool = true,
  fuzz_values? : Array[Bytes] = [],
) -> Field raise ModelError {
  guard !values.is_empty() else { raise Invalid("empty group") }
  let value = default_value.unwrap_or(values[0])
  let saved : Array[Bytes] = []
  let mut removed = false
  for item in values {
    if !removed && item == value {
      removed = true
    } else {
      saved.push(item)
    }
  }
  let base = Field::simple(value, saved, fuzzable~)
  Field::with_fuzz_values(base, fuzz_values, fuzzable)
}

///|
pub fn Field::default_value(self : Field) -> Bytes {
  self.value
}

///|
pub fn Field::num_mutations(self : Field) -> Int {
  self.count
}

///|
pub fn Field::mutation(self : Field, index : Int) -> Bytes? {
  if index < 0 || index >= self.count {
    None
  } else {
    Some((self.candidate)(index))
  }
}