///|
/// The generated artifact: one problem, its answers, why it is rated the way it
/// is, and the certificate that lets anyone re-check it.
pub(all) struct Item {
  schema_name : String
  question : String
  answers : Array[Rat]
  level : Int32
  reasons : Array[String]
  fingerprint : String
  certificate : Certificate
  robust : Bool
  signature : String
  family : Family
  ///
  /// Structural tags used by the assembler to keep a paper diverse, e.g. two
  /// items in a row that both have a zero root.
  tags : Array[String]
}

///|
pub fn Item::answer_text(self : Item) -> String {
  let subs = ["₁", "₂", "₃", "₄"]
  let sb = StringBuilder()
  if self.answers.length() == 1 {
    sb.write_string("x = \{self.answers[0].to_string()}")
  } else {
    for i, r in self.answers {
      if i > 0 {
        sb.write_string(",")
      }
      sb.write_string("x")
      if i < subs.length() {
        sb.write_string(subs[i])
      }
      sb.write_string(" = \{r.to_string()}")
    }
  }
  sb.to_string()
}

///|
pub fn Item::level_label(self : Item) -> String {
  "L\{self.level}"
}

///|
pub fn Item::reasons_text(self : Item) -> String {
  if self.reasons.length() == 0 {
    "基础"
  } else {
    let sb = StringBuilder()
    for i, r in self.reasons {
      if i > 0 {
        sb.write_string("、")
      }
      sb.write_string(r)
    }
    sb.to_string()
  }
}

///|
/// Rebuild the object under test from a certificate payload. For the quadratic
/// family the payload is `[a, b, c]` in that order.
pub fn rebuild_quadratic(cert : Certificate) -> Poly {
  let p = cert.payload
  Poly::new([p[2], p[1], p[0]])
}

///|
/// A generation run. The counters are part of the product: "1083 candidates,
/// 0 rejected, 41 duplicates" is a much better claim than "it works".
pub(all) struct Pool {
  items : Array[Item]
  attempted : Int32
  rejected : Int32
  duplicates : Int32
  rejects : Array[String]
}

///|
pub fn Pool::size(self : Pool) -> Int32 {
  self.items.length()
}

///|
pub fn Pool::count_at(self : Pool, level : Int32) -> Int32 {
  let mut n = 0
  for it in self.items {
    if it.level == level {
      n = n + 1
    }
  }
  n
}

///|
pub fn Pool::level_histogram(self : Pool) -> String {
  let sb = StringBuilder()
  for level in 1..<=5 {
    if level > 1 {
      sb.write_string("  ")
    }
    sb.write_string("L\{level}:\{self.count_at(level)}")
  }
  sb.to_string()
}

///|
priv struct Step {
  name : String
  domain : (Int32, Int32)?
  expr : Expr?
}

///|
fn build_steps(schema : Schema) -> Array[Step] {
  let out : Array[Step] = []
  for v in schema.answers {
    out.push({ name: v.name, domain: v.domain, expr: v.expr, })
  }
  for v in schema.params {
    out.push({ name: v.name, domain: v.domain, expr: v.expr, })
  }
  out
}

///|
/// Bind the first `upto` steps into an environment.
fn env_of(
  steps : Array[Step],
  values : Array[Rat],
  upto : Int32,
) -> Map[String, Rat] {
  let env : Map[String, Rat] = Map([])
  for i in 0.. Unit {
  if idx == steps.length() {
    out.push(values.copy())
    return
  }
  let step = steps[idx]
  match step.domain {
    Some(bounds) => {
      let (lo, hi) = bounds
      let mut v = lo
      while v <= hi {
        values[idx] = rat_int(v.to_int64())
        walk(steps, idx + 1, values, out)
        v = v + 1
      }
    }
    None =>
      match step.expr {
        Some(e) => {
          let env = env_of(steps, values, idx)
          match e.eval(env) {
            Some(v) => {
              values[idx] = v
              walk(steps, idx + 1, values, out)
            }
            None => ()
          }
        }
        None => ()
      }
  }
}

///|
fn requirements_hold(
  schema : Schema,
  steps : Array[Step],
  values : Array[Rat],
) -> Bool {
  let env = env_of(steps, values, steps.length())
  let mut ok = true
  for r in schema.requires {
    match r.check(env) {
      Some(true) => ()
      _ => ok = false
    }
  }
  ok
}

///|
fn make_item(schema : Schema, env : Map[String, Rat]) -> Result[Item, String] {
  match schema.family {
    FQuadratic => build_quadratic(schema, env)
    FRational => build_rational(schema, env)
  }
}

///|
/// Generate a pool of verified items.
///
/// `limit = 0` means "no limit". Items are produced in a deterministic order,
/// and the first occurrence of a fingerprint wins, so the same schema always
/// yields the same pool.
pub fn generate(schema : Schema, limit : Int32) -> Pool {
  let steps = build_steps(schema)
  let seed_values : Array[Rat] = []
  for _ in 0.. 0 && items.length() >= limit {
      break
    }
    if !requirements_hold(schema, steps, values) {
      continue
    }
    attempted = attempted + 1
    let env = env_of(steps, values, steps.length())
    match make_item(schema, env) {
      Ok(item) =>
        if contains_str(seen, item.fingerprint) {
          duplicates = duplicates + 1
        } else {
          seen.push(item.fingerprint)
          items.push(item)
        }
      Err(reason) => {
        rejected = rejected + 1
        rejects.push(reason)
      }
    }
  }
  { items, attempted, rejected, duplicates, rejects, }
}

///|
test "generation only emits well-posed items and dedupes by fingerprint" {
  let schema = match load_schema(SAMPLE_SCHEMA) {
    Ok(s) => s
    Err(e) => abort("schema error: \{e}")
  }
  let pool = generate(schema, 0)
  inspect(pool.size() > 0, content="true")
  inspect(pool.rejected, content="0")
  // 19 * 19 root pairs, 171 of them satisfy r1 < r2.
  inspect(pool.attempted, content="171")
  // With a fixed leading coefficient no two distinct parameter tuples describe
  // the same equation, so nothing collapses.
  inspect(pool.duplicates, content="0")
}

///|
test "every generated item re-verifies independently" {
  let schema = match load_schema(SAMPLE_SCHEMA) {
    Ok(s) => s
    Err(e) => abort("schema error: \{e}")
  }
  let pool = generate(schema, 120)
  let mut bad = 0
  for item in pool.items {
    let p = rebuild_quadratic(item.certificate)
    let (v, _) = verify_quadratic(p, item.answers)
    if !v.is_well_posed() {
      bad = bad + 1
    }
  }
  inspect(bad, content="0")
}

///|
/// A schema that *does* produce scale duplicates, to prove the fingerprint
/// collapses them instead of inflating the pool.
const DUP_SCHEMA : String =
  #|schema "quadratic_dup"
  #|family quadratic
  #|answer {
  #|  r1: int in [-4, 4]
  #|  r2: int in [-4, 4]
  #|}
  #|param {
  #|  a: int in [1, 3]
  #|  b: -(a * (r1 + r2))
  #|  c: a * r1 * r2
  #|}
  #|require {
  #|  r1 < r2
  #|}
  #|ask "解方程 {poly} = 0"
  #|

///|
test "scaled duplicates collapse into one item" {
  let schema = match load_schema(DUP_SCHEMA) {
    Ok(s) => s
    Err(e) => abort("schema error: \{e}")
  }
  let pool = generate(schema, 0)
  inspect(pool.attempted > 0, content="true")
  inspect(pool.duplicates > 0, content="true")
  // Every surviving item is the primitive representative of its class.
  let p = rebuild_quadratic(pool.items[0].certificate)
  inspect(p.leading().to_string(), content="1")
}

///|
test "generation is reproducible" {
  let schema = match load_schema(SAMPLE_SCHEMA) {
    Ok(s) => s
    Err(_) => abort("schema error")
  }
  let a = generate(schema, 50)
  let b = generate(schema, 50)
  inspect(a.size(), content="50")
  let mut same = true
  for i in 0..