// Two MoonBit replicas edit the same text concurrently, exchange updates in
// the Yjs v1 format and converge. The final state is printed as base64 so
// that it can be loaded by Yjs (see README: "Reading the result with Yjs").
//
//   moon run examples/concurrent

///|
fn main {
  // Every replica needs its own client id (Yjs uses random 32-bit ids).
  let alice = @doc.Doc::new(1001)
  let bob = @doc.Doc::new(2002)

  // Collect the updates each replica produces, like `doc.on('update')`.
  let from_alice : Array[Bytes] = []
  let from_bob : Array[Bytes] = []
  alice.on_update((u, is_local) => if is_local { from_alice.push(u) })
  bob.on_update((u, is_local) => if is_local { from_bob.push(u) })

  let alice_text = @types.Text::new(alice, "doc")
  let bob_text = @types.Text::new(bob, "doc")

  // A shared starting point.
  alice_text.insert(0, "Hello world")
  for u in from_alice {
    bob.apply_update(u) catch {
      e => abort(e.to_string())
    }
  }
  from_alice.clear()
  println("start:  \{alice_text}")

  // Both edit the same position at the same time, without seeing each other.
  alice_text.insert(5, ", Alice")
  bob_text.insert(5, ", Bob")
  bob_text.delete(0, 1)
  bob_text.insert(0, "h")
  println("alice:  \{alice_text}")
  println("bob:    \{bob_text}")

  // Exchange the updates (in reverse order, to show that order does not
  // matter: updates whose dependencies are missing wait in a queue).
  for u in from_bob.rev() {
    alice.apply_update(u) catch {
      e => abort(e.to_string())
    }
  }
  for u in from_alice {
    bob.apply_update(u) catch {
      e => abort(e.to_string())
    }
  }
  println("merged: \{alice_text}")
  println("bob:    \{bob_text}")
  guard alice_text.to_string() == bob_text.to_string() else {
    abort("replicas did not converge")
  }

  // The whole document as one Yjs v1 update.
  let state = alice.encode_state_as_update()
  println("update (base64): \{@base64.encode(state)}")
}