// Simulation engine.
//
// Implemented: all node kinds — Pool / Source / Drain / Gate / Converter /
// Trader / Delay / Queue / Register — with fixed/dice/interval/all rates,
// automatic / start / interactive activation, pull / pull-all / push flow,
// capacity, and state connections (modifier / activator — see state.mbt). Each
// step resolves direct resource edges against a working copy of the previous
// values (contention on a shared pool resolved deterministically in edge
// declaration order — a documented MVP rule), then the gate / converter+trader /
// delay+queue passes, then interactive activations are cleared. Still to come:
// trigger and rate-label state connections.

///|
/// Fresh per-node time buffers (Delay line / Queue store; empty for others).
fn make_buffers(diagram : Diagram) -> Array[Array[Int]] {
  let bufs : Array[Array[Int]] = []
  for i in 0.. Array::make(if node.param > 0 { node.param } else { 1 }, 0)
      Queue => Array::make(1, 0)
      _ => []
    }
    bufs.push(buf)
  }
  bufs
}

///|
/// Create the initial simulation state for a diagram, seeded for reproducibility.
pub fn SimState::init(
  diagram : Diagram,
  seed? : UInt64 = 0x123456789ABCDEF,
) -> SimState {
  let n = diagram.nodes.length()
  let values = Array::make(n, 0)
  for i in 0.. Unit {
  if node >= 0 && node < self.pending.length() {
    self.pending[node] = true
  }
}

///|
/// Reset state back to the diagram's initial values (keeps the RNG stream).
pub fn SimState::reset(self : SimState, diagram : Diagram) -> Unit {
  let n = diagram.nodes.length()
  let values = Array::make(n, 0)
  for i in 0.. Bool {
  match kind {
    Gate | Converter | Trader | Delay | Queue | Register => true
    _ => false
  }
}

///|
fn node_active(diagram : Diagram, state : SimState, idx : Int) -> Bool {
  // An unsatisfied activator disables the node entirely.
  if node_blocked(diagram, state, idx) {
    return false
  }
  let node = diagram.nodes[idx]
  match node.activation {
    Automatic => true
    Start => state.step == 0
    // Interactive (and, until triggers are wired, OnTrigger) fire when the host
    // has queued an activation for this node via `activate`.
    Interactive | OnTrigger =>
      idx >= 0 && idx < state.pending.length() && state.pending[idx]
    // Passive nodes never self-fire; they still supply/receive when pulled/pushed.
    Passive => false
  }
}

///|
/// Resolve a rate label to a concrete amount for this step. `avail` is the
/// amount available at the source end (used by `All`).
fn resolve_rate(rate : Rate, state : SimState, avail : Int) -> Int {
  match rate {
    Fixed(n) => n
    Dice(count, sides) => roll_dice(state, count, sides)
    Interval(n) => if n > 0 && state.step % n == 0 { 1 } else { 0 }
    All => avail
  }
}

///|
/// Advance the simulation by one step, mutating `state`.
pub fn step(diagram : Diagram, state : SimState) -> Unit {
  let n = diagram.nodes.length()
  let work = Array::make(n, 0)
  for i in 0.. 0 // placeholder; `want` is taken as-is for sources below
      _ => work[edge.from]
    }
    let want = match from.kind {
      Source =>
        effective_rate(diagram, state, ei, resolve_rate(edge.rate, state, 0))
      _ =>
        effective_rate(
          diagram,
          state,
          ei,
          resolve_rate(edge.rate, state, stock),
        )
    }
    let avail = match from.kind {
      Source => want
      _ => work[edge.from]
    }
    let mut moved = if want < avail { want } else { avail }

    // PullAll is atomic: if the source cannot fully satisfy, nothing moves.
    if to.flow == PullAll && moved < want {
      moved = 0
    }

    // Capacity at the destination (drains/sources never store).
    match to.capacity {
      Some(cap) => {
        let room = cap - work[edge.to]
        if moved > room {
          moved = room
        }
      }
      None => ()
    }
    if moved < 0 {
      moved = 0
    }

    // Apply: sources are infinite producers; drains destroy what they receive.
    match from.kind {
      Source => ()
      _ => work[edge.from] = work[edge.from] - moved
    }
    match to.kind {
      Drain | Source => ()
      _ => work[edge.to] = work[edge.to] + moved
    }
    flows[ei] = moved
  }
  process_gates(diagram, state, work, flows)
  process_converters(diagram, state, work, flows)
  process_time_nodes(diagram, state, work, flows)
  // Interactive activations are consumed by this step only.
  for i in 0.. Unit {
  for _ in 0..