// Copyright 2015 The etcd Authors
// 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.

///|
/// Build AppendEntries arguments carrying every entry after `prev` (the
/// leader's nextIndex − 1 for a follower), used to replicate or repair.
fn Node::append_args_from(self : Node, prev : UInt64) -> AppendEntriesArgs {
  let entries : Array[Entry] = []
  let last = self.last_log_index()
  let mut i = prev
  while i < last {
    entries.push(self.log[(i - self.snapshot_index).to_int()])
    i = i + 1
  }
  {
    term: self.current_term,
    leader_id: self.id,
    prev_log_index: prev,
    prev_log_term: self.term_at(prev),
    entries,
    leader_commit: self.commit_index,
  }
}

///|
/// Replicate the leader's log to one follower, backing off the previous index
/// on rejection until the two logs agree (Raft's nextIndex decrement). Returns
/// whether the follower now holds the leader's log.
fn bring_into_sync(leader : Node, follower : Node) -> Bool {
  let mut prev = leader.last_log_index()
  let mut running = true
  let mut synced = false
  while running {
    let reply = follower.handle_append_entries(leader.append_args_from(prev))
    if reply.term > leader.current_term() {
      leader.become_follower(reply.term)
      running = false
    } else if reply.success {
      synced = true
      running = false
    } else {
      // A rejection is a log-matching failure, which can only occur above the
      // shared baseline (index 0 always agrees at equal term), so `prev` is
      // strictly positive here and the decrement never underflows: the walk-back
      // terminates once it reaches the baseline and the append is accepted.
      prev = prev - 1
    }
  }
  synced
}

///|
/// Drive one replication round from `leader` to every follower and advance the
/// commit index once a majority (the leader included) store the last entry.
/// Returns the resulting commit index.
pub fn replicate(leader : Node, followers : Array[Node]) -> UInt64 {
  let last = leader.last_log_index()
  let mut in_sync = 1
  for f in followers {
    if bring_into_sync(leader, f) {
      in_sync = in_sync + 1
    }
  }
  let majority = (followers.length() + 1) / 2 + 1
  if in_sync >= majority {
    leader.advance_commit(last)
  }
  leader.commit_index
}