///|
priv struct Input {
  nodes : Map[String, Int32]
  visible : Array[Bool]
  containers : Array[Int32?]
}

///|
fn input(view : @model.Bpmn, budget : Budget) -> Input? {
  let result = view.scope(
    limits=@model.ScopeLimits::new(
      nodes=budget.limits.nodes,
      work=budget.limits.work - budget.work,
    ),
  )
  guard result.scope is Some(scope) else {
    budget.diagnostics.append(result.diagnostics)
    budget.failed = true
    return None
  }
  if !budget.charge(scope.work) {
    return None
  }
  let nodes = Map([])
  for i, node in view.nodes {
    nodes[node.id] = i
  }
  Some({
    nodes,
    visible: scope.visible.iter().to_array(),
    containers: scope.containers.iter().to_array(),
  })
}

///|
pub fn activity(kind : @model.NodeKind) -> Bool {
  match kind {
    Task
    | UserTask
    | ServiceTask
    | SendTask
    | ReceiveTask
    | ManualTask
    | BusinessRuleTask
    | ScriptTask
    | SubProcess
    | AdHocSubProcess
    | Transaction
    | CallActivity => true
    _ => false
  }
}

///|
fn event(kind : @model.NodeKind) -> Bool {
  match kind {
    StartEvent
    | EndEvent
    | IntermediateCatchEvent
    | IntermediateThrowEvent
    | BoundaryEvent => true
    _ => false
  }
}

///|
fn gateway(kind : @model.NodeKind) -> Bool {
  match kind {
    ExclusiveGateway
    | InclusiveGateway
    | ParallelGateway
    | ComplexGateway
    | EventBasedGateway => true
    _ => false
  }
}

///|
fn visible_endpoint(
  id : String,
  view : @model.Bpmn,
  input : Input,
  budget : Budget,
) -> Int32? {
  guard input.nodes.get(id) is Some(start) else { return None }
  let mut i = start
  while !input.visible[i] {
    if !budget.charge(1) {
      return None
    }
    if view.nodes[i].ownership.pool is Some(pool) &&
      view.nodes[input.nodes[pool]].black_box {
      i = input.nodes[pool]
    } else if input.containers[i] is Some(parent) {
      i = parent
    } else if view.nodes[i].ownership.lanes
      .last()
      .bind(id => input.nodes.get(id))
      is Some(parent) {
      i = parent
    } else if view.nodes[i].ownership.attached_to.bind(id => input.nodes.get(id))
      is Some(parent) {
      i = parent
    } else {
      return None
    }
  }
  Some(i)
}