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

///|
/// A single Raft node: its identity, current role, and the state it holds.
///
/// `current_term`, `voted_for` and `log` make up the state a real deployment
/// must persist to stable storage before replying to any RPC, so the node
/// recovers correctly after a crash. `commit_index` and `last_applied` are
/// volatile: they are safe to lose and are rebuilt after a restart.
pub struct Node {
  id : String
  mut role : Role
  mut current_term : UInt64
  mut voted_for : String?
  log : Array[Entry]
  mut commit_index : UInt64
  mut last_applied : UInt64
  mut snapshot_index : UInt64
  mut snapshot_term : UInt64
  // How many snapshots this node has *installed from a leader*. A local
  // compaction also advances `snapshot_index` — it moves the durable floor — but
  // installs nothing, so only this counter makes a snapshot real to the driver.
  mut snapshots_installed : UInt64
}

///|
/// Create a fresh node that starts as a follower at term 0 with an empty log.
pub fn Node::new(id : String) -> Node {
  {
    id,
    role: Follower,
    current_term: 0,
    voted_for: None,
    log: [],
    commit_index: 0,
    last_applied: 0,
    snapshot_index: 0,
    snapshot_term: 0,
    snapshots_installed: 0,
  }
}

///|
/// The node's current role.
pub fn Node::role(self : Node) -> Role {
  self.role
}

///|
/// The node's current term.
pub fn Node::current_term(self : Node) -> UInt64 {
  self.current_term
}

///|
/// Step down to follower and adopt a newly observed, higher term, clearing
/// any vote cast in the old term.
pub fn Node::become_follower(self : Node, term : UInt64) -> Unit {
  self.role = Follower
  // etcd's `reset` only clears a vote when the term actually moves; a same-term
  // step-down (check-quorum, a lost election) keeps the vote it cast, which is
  // what leaves the HardState unchanged and the next Ready MustSync=false.
  if self.current_term != term {
    self.current_term = term
    self.voted_for = None
  }
}

///|
/// Enter the pre-vote probe phase (etcd's `becomePreCandidate`): change the role
/// but NOT the term or vote — a pre-candidate solicits votes under a hypothetical
/// next term it has not adopted.
pub fn Node::become_pre_candidate(self : Node) -> Unit {
  self.role = PreCandidate
}

///|
/// Start a new election: advance the term and vote for self.
pub fn Node::become_candidate(self : Node) -> Unit {
  self.current_term = self.current_term + 1
  self.role = Candidate
  self.voted_for = Some(self.id)
}

///|
/// Take leadership after winning a majority of votes in the current term.
pub fn Node::become_leader(self : Node) -> Unit {
  self.role = Leader
}

///|
/// Advance the commit index once a higher index is known to be committed.
/// Never moves backwards.
pub fn Node::advance_commit(self : Node, index : UInt64) -> Unit {
  if index > self.commit_index {
    self.commit_index = index
  }
}

///|
/// Record that the state machine has applied entries up through `index`.
pub fn Node::mark_applied(self : Node, index : UInt64) -> Unit {
  self.last_applied = index
}