docs: @SuppressWarnings({"checkstyle:npathcomplexity", "checkstyle:cyclomaticcomplexity", "checkstyle:methodlength"})
      public void sendAppendRequest(Endpoint follower) {
          if (!raftIntegration.isReachable(follower)) {
              return;
          }
  
          RaftLog raftLog = state.log();
          LeaderState leaderState = state.leaderState();
          FollowerState followerState = leaderState.getFollowerState(follower);
          if (followerState.isAppendRequestBackoffSet()) {
              // The follower still has not sent a response for the last append request.
              // We will send a new append request either when the follower sends a response
              // or a back-off timeout occurs.
              return;
          }
  
          long nextIndex = followerState.nextIndex();
  
          // if the first log entry to be sent is put into the snapshot, check if we still keep it in the log
          // if we still keep that log entry and its previous entry, we don't need to send a snapshot
          if (nextIndex <= raftLog.snapshotIndex()
                  && (!raftLog.containsLogEntry(nextIndex) || (nextIndex > 1 && !raftLog.containsLogEntry(nextIndex - 1)))) {
              InstallSnapshot installSnapshot = new InstallSnapshot(localMember, state.term(), raftLog.snapshot());
              if (logger.isFineEnabled()) {
                  logger.fine("Sending " + installSnapshot + " to " + follower + " since next index: " + nextIndex
                          + " <= snapshot index: " + raftLog.snapshotIndex());
              }
              followerState.setMaxAppendRequestBackoff();
              scheduleAppendAckResetTask();
              raftIntegration.send(installSnapshot, follower);
              return;
          }
  
          int prevEntryTerm = 0;
          long prevEntryIndex = 0;
          LogEntry[] entries;
          boolean setAppendRequestBackoff = true;
  
          if (nextIndex > 1) {
              prevEntryIndex = nextIndex - 1;
              LogEntry prevEntry = (raftLog.snapshotIndex() == prevEntryIndex)
                      ? raftLog.snapshot() : raftLog.getLogEntry(prevEntryIndex);
              assert prevEntry != null : "Prev entry index: " + prevEntryIndex + ", snapshot: " + raftLog.snapshotIndex();
              prevEntryTerm = prevEntry.term();
  
              long matchIndex = followerState.matchIndex();
              if (matchIndex == 0) {
                  // Until the leader has discovered where it and the follower's logs match,
                  // the leader can send AppendEntries with no entries (like heartbeats) to save bandwidth.
                  // We still need to enable append request backoff here because we do not want to bombard
                  // the follower before we learn its match index
                  entries = new LogEntry[0];
              } else if (nextIndex <= raftLog.lastLogOrSnapshotIndex()) {
                  // Then, once the matchIndex immediately precedes the nextIndex,
                  // the leader should begin to send the actual entries
                  long end = min(nextIndex + appendRequestMaxEntryCount, raftLog.lastLogOrSnapshotIndex());
                  entries = raftLog.getEntriesBetween(nextIndex, end);
              } else {
                  // The follower has caught up with the leader. Sending an empty append request as a heartbeat...
                  entries = new LogEntry[0];
                  setAppendRequestBackoff = false;
              }
          } else if (nextIndex == 1 && raftLog.lastLogOrSnapshotIndex() > 0) {
              // Entries will be sent to the follower for the first time...
              long end = min(nextIndex + appendRequestMaxEntryCount, raftLog.lastLogOrSnapshotIndex());
              entries = raftLog.getEntriesBetween(nextIndex, end);
          } else {
              // There is no entry in the Raft log. Sending an empty append request as a heartbeat...
              entries = new LogEntry[0];
              setAppendRequestBackoff = false;
          }
  
          AppendRequest request = new AppendRequest(getLocalMember(), state.term(), prevEntryTerm, prevEntryIndex,
                  state.commitIndex(), entries);
  
          if (logger.isFineEnabled()) {
              logger.fine("Sending " + request + " to " + follower + " with next index: " + nextIndex);
          }
  
          if (setAppendRequestBackoff) {
              followerState.setAppendRequestBackoff();
              scheduleAppendAckResetTask();
          }
  
          send(request, follower);
      }