@startuml hb_last_indexer
title Protection last-indexer → reconnectToSeeds → retryUntilSeedResponds

participant "Node A" as NodeA
participant "Indexer LAST\n(seul restant)" as IL
participant "Seed Indexer\n(config)" as SEED
participant "DHT" as DHT

note over NodeA: Pool = 1 indexeur (LAST)\nIsSeed=false, score bas depuis longtemps

== Tentative d'éviction par score ==
NodeA -> NodeA: score < minScore\nAND TotalOnline ≥ 2×interval\nAND !IsSeed\nAND len(pool) > 1 ← FAUX : pool == 1

note over NodeA: Garde active : len(pool) == 1\n→ éviction par score BLOQUÉE\nLAST reste dans le pool

== Panne réseau (heartbeat fail) ==
NodeA -> IL: stream.Encode(Heartbeat{...})
IL -->x NodeA: timeout

NodeA -> NodeA: HeartbeatFailure → evictPeer(LAST)\npoolSize = 0

NodeA -> NodeA: reconnectToSeeds()\n→ parse IndexerAddresses (conf)\n→ SetAddr + SetScore(IsSeed=true) pour chaque seed

alt seeds ajoutés (IndexerAddresses non vide)
    NodeA -> NodeA: NudgeIt() → tick immédiat
    NodeA -> SEED: Heartbeat{...} (via SendHeartbeat nudge)
    SEED --> NodeA: HeartbeatResponse{fillRate, ...}
    note over NodeA: Pool rétabli via seeds.\nDHT proactive discovery reprend.

else IndexerAddresses vide
    NodeA -> NodeA: go retryUntilSeedResponds()
    note over NodeA: panic immédiat :\n"pool is empty and no seed indexers configured"\n→ arrêt du processus
end

== retryUntilSeedResponds (si seeds non répondants) ==
loop backoff exponentiel (10s → 20s → ... → 5min)
    NodeA -> NodeA: time.Sleep(backoff)
    NodeA -> NodeA: len(Indexers.GetAddrs()) > 0?\n→ oui : retour (quelqu'un a refillé)
    NodeA -> NodeA: reconnectToSeeds()
    alt pool > 0 après reconnect
        NodeA -> NodeA: NudgeIt()\nDHT.Bootstrap(ctx, 15s)
        note over NodeA: Sortie de la boucle.\nHeartbeat normal reprend.
    end
end

@enduml
