// ============================================================
// Execution safety policies
// ============================================================

///|
/// Limit the total number of child ticks during one activation.
/// This protects service loops from accidental infinite-running subtrees.
pub fn execution_budget_node(child : Node, max_ticks : Int) -> Node {
  let used : Ref[Int] = Ref::new(0)
  let budget = if max_ticks > 0 { max_ticks } else { 1 }
  let tick = fn(bb) {
    if used.get() >= budget {
      child.reset()
      used.set(0)
      Status::BTFailure
    } else {
      used.set(used.get() + 1)
      match child.tick(bb) {
        Status::BTSuccess => {
          used.set(0)
          Status::BTSuccess
        }
        Status::BTFailure => {
          used.set(0)
          Status::BTFailure
        }
        Status::BTRunning => Status::BTRunning
      }
    }
  }
  let reset = fn() {
    used.set(0)
    child.reset()
  }
  Node::new(tick, reset)
}

///|
/// Fail when a predicate is false, otherwise delegate to the child.
pub fn require_node(predicate : (Blackboard) -> Bool, child : Node) -> Node {
  let tick = fn(bb) {
    if predicate(bb) {
      child.tick(bb)
    } else {
      child.reset()
      Status::BTFailure
    }
  }
  let reset = fn() { child.reset() }
  Node::new(tick, reset)
}

///|
/// Execute a child at most once per activation and remember its outcome.
pub fn once_node(child : Node) -> Node {
  let completed : Ref[Status?] = Ref::new(None)
  let tick = fn(bb) {
    match completed.get() {
      Some(status) => status
      None => {
        let status = child.tick(bb)
        match status {
          Status::BTSuccess | Status::BTFailure => completed.set(Some(status))
          Status::BTRunning => ()
        }
        status
      }
    }
  }
  let reset = fn() {
    completed.set(None)
    child.reset()
  }
  Node::new(tick, reset)
}