///|
/// Exact residual verification for a deterministic candidate tree.
/// Each sibling is a point-mass proposal: accept with its current residual
/// probability; after rejection remove that token and renormalize. Raw draft
/// probabilities guide candidate construction, not this acceptance law.
pub enum TreeEvaluateError {
  TreeInvalid
  TargetLengthMismatch
  RandomLengthMismatch
  InvalidTarget(Int)
  InvalidRandom(Int)
  InconsistentSiblings(Int)
} derive(Eq, Debug)

///|
pub struct TreeEvaluation {
  accepted_nodes : Array[Int]
  rejected_nodes : Array[Int]
  skipped_nodes : Array[Int]
  committed_path : Array[Int]
  emitted : Array[Int]
}

///|
pub fn TreeEvaluation::evaluated_count(self : TreeEvaluation) -> Int {
  self.accepted_nodes.length() + self.rejected_nodes.length()
}

///|
fn contains_id(values : Array[Int], target : Int) -> Bool {
  for value in values {
    if value == target {
      return true
    }
  }
  false
}

///|
/// Targets in node order score the prefix BEFORE each node. Siblings must
/// share one target row. Acceptance draws are indexed by node; fallback draws
/// by depth minus one. Draws must be independent of candidate construction and
/// one another. At a leaf this round ends; the next round produces more tokens.
pub fn evaluate_tree(
  tree : DraftTree,
  target_distributions : Array[Array[Double]],
  uniforms : Array[Double],
  fallback_uniforms : Array[Double],
) -> Result[TreeEvaluation, TreeEvaluateError] {
  match validate_tree(tree) {
    Err(_) => return Err(TreeInvalid)
    Ok(_) => ()
  }
  if target_distributions.length() != tree.nodes.length() {
    return Err(TargetLengthMismatch)
  }
  if uniforms.length() != tree.nodes.length() ||
    fallback_uniforms.length() != tree.max_depth() {
    return Err(RandomLengthMismatch)
  }
  for i in 0.. value
        Err(_) => return Err(InvalidTarget(node.id))
      }
    }
    match selected {
      Some(id) => parent = Some(id)
      None => {
        let token = match
          sample_categorical(residual, fallback_uniforms[depth]) {
          Ok(value) => value
          Err(_) => return Err(InvalidTarget(siblings[0]))
        }
        emitted.push(token)
        break
      }
    }
    depth = depth + 1
  }
  let skipped : Array[Int] = []
  for node in tree.nodes {
    if !contains_id(accepted, node.id) && !contains_id(rejected, node.id) {
      skipped.push(node.id)
    }
  }
  Ok({
    accepted_nodes: accepted,
    rejected_nodes: rejected,
    skipped_nodes: skipped,
    committed_path: path,
    emitted,
  })
}

///|
pub fn render_tree_evaluation(value : TreeEvaluation) -> String {
  "accepted_nodes=" +
  value.accepted_nodes.length().to_string() +
  "\nrejected_nodes=" +
  value.rejected_nodes.length().to_string() +
  "\nskipped_nodes=" +
  value.skipped_nodes.length().to_string() +
  "\ncommitted_depth=" +
  value.committed_path.length().to_string() +
  "\nemitted_tokens=" +
  value.emitted.length().to_string() +
  "\n"
}