// ============================================================
// Blackboard-driven routing policies
// ============================================================

///|
/// Route to one child using a string key on the blackboard.
pub fn route_node(
  key : String,
  routes : Map[String, Node],
  fallback : Node,
) -> Node {
  let tick = fn(bb : Blackboard) {
    match bb.get_string(key) {
      Some(route) =>
        match routes.get(route) {
          Some(child) => child.tick(bb)
          None => fallback.tick(bb)
        }
      None => fallback.tick(bb)
    }
  }
  let reset = fn() {
    routes.each(fn(_, child) { child.reset() })
    fallback.reset()
  }
  Node::new(tick, reset)
}

///|
/// Run a child only while a blackboard integer is within an inclusive range.
pub fn int_range_guard_node(
  key : String,
  minimum : Int,
  maximum : Int,
  child : Node,
) -> Node {
  let tick = fn(bb : Blackboard) {
    match bb.get_int(key) {
      Some(value) =>
        if value >= minimum && value <= maximum {
          child.tick(bb)
        } else {
          child.reset()
          Status::BTFailure
        }
      None => {
        child.reset()
        Status::BTFailure
      }
    }
  }
  let reset = fn() { child.reset() }
  Node::new(tick, reset)
}

///|
/// Set a blackboard value after a child completes successfully.
pub fn set_int_on_success_node(key : String, value : Int, child : Node) -> Node {
  let tick = fn(bb : Blackboard) {
    let status = child.tick(bb)
    if status == Status::BTSuccess {
      bb.set_int(key, value)
    }
    status
  }
  let reset = fn() { child.reset() }
  Node::new(tick, reset)
}

///|
/// Set a blackboard value after a child fails.
pub fn set_bool_on_failure_node(
  key : String,
  value : Bool,
  child : Node,
) -> Node {
  let tick = fn(bb : Blackboard) {
    let status = child.tick(bb)
    if status == Status::BTFailure {
      bb.set_bool(key, value)
    }
    status
  }
  let reset = fn() { child.reset() }
  Node::new(tick, reset)
}