// 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.

///|
/// Learners: non-voting members that receive the log but never campaign and
/// never count toward a quorum. Add `d` as a learner, show it joins the
/// configuration without joining the voter set, prove it cannot elect a leader
/// on its own, then promote it to a full voter.
///
///     moon run src/examples/06-learner
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 voters(cluster : @raft.Cluster) -> String {
  let vs = cluster.node("a").voter_nodes()
  vs.sort()
  vs.join(",")
}

///|
fn learners(cluster : @raft.Cluster) -> String {
  let ls = cluster.node("a").conf_state().learners
  ls.sort()
  ls.join(",")
}

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

  // Add d as a learner (non-voting).
  let _ = cluster.propose_conf(@raft.ConfChange::add_learner("d"))
  commit_to(cluster, 200)

  println("voters:  \{voters(cluster)}")
  println("learners: \{learners(cluster)}")

  // A learner cannot vote or campaign: with every voter down, no leader appears.
  cluster.crash("a")
  cluster.crash("b")
  cluster.crash("c")
  let elected = cluster.run_until_leader(400)
  println("only a learner up -> leader elected: \{elected is Some(_)}")

  // Bring the voters back and promote the learner to a full voter.
  cluster.restart("a")
  cluster.restart("b")
  cluster.restart("c")
  let _ = cluster.run_until_leader(200)
  let _ = cluster.propose_conf(@raft.ConfChange::add("d"))
  commit_to(cluster, 200)
  println("after promoting d -> voters:  \{voters(cluster)}")
  println("after promoting d -> learners: \{learners(cluster)}")
}