// Copyright 2026 Leo Cheng
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// Membership changes, one server at a time. Add a fourth voter, watch the
/// quorum grow, then remove one and watch it shrink - the cluster keeps
/// agreeing and the safety invariants keep holding the whole way through.
///
///     moon run src/examples/05-membership
fn commit_to(cluster : @raft.Cluster, max_ticks : Int32) -> Unit {
  match cluster.leader() {
    Some(l) => {
      let target = cluster.node(l).node().last_log_index()
      let _ = cluster.run_until_committed(target, max_ticks)
    }
    None => ()
  }
}

///|
fn show(title : String, cluster : @raft.Cluster) -> Unit {
  let vs = cluster.node("a").voter_nodes()
  vs.sort()
  println("\{title}: \{vs.join(",")}")
}

///|
fn main {
  let ids = ["a", "b", "c"]
  let cluster = @raft.Cluster::new(ids, seed=7)
  let _ = cluster.run_until_leader(200)
  let _ = cluster.propose(b"init")
  commit_to(cluster, 200)

  show("voters at start      ", cluster)

  // Add a fourth voter. The change is an ordinary log entry, so every node
  // folds it in at the same position once it commits (ยง6).
  let _ = cluster.propose_conf(@raft.ConfChange::add("d"))
  commit_to(cluster, 200)
  show("after adding d        ", cluster)

  // Remove a voter. Quorum math shifts from 2/3 to 2/4.
  let _ = cluster.propose_conf(@raft.ConfChange::remove("c"))
  commit_to(cluster, 200)
  show("after removing c      ", cluster)

  // The cluster is still a working cluster after reconfiguration.
  let _ = cluster.propose(b"after-reconfig")
  commit_to(cluster, 200)
  println("still agrees after reconfig: \{cluster.committed_agrees()}")
  println("safety invariants hold:       \{cluster.invariants_hold()}")
}