//! The path table (`ARCHITECTURE.md` §3.6).

use std::collections::HashMap;
use std::time::Instant;

use plexus_core::AddressHash;

use crate::InterfaceId;

/// A learned path: "for destination D, send via next-hop H over interface I".
#[derive(Debug, Clone)]
pub struct PathEntry {
    /// Destination this path reaches.
    pub destination: AddressHash,
    /// Identity of the relaying transport node.
    pub next_hop: AddressHash,
    /// Interface to send over.
    pub interface: InterfaceId,
    /// Distance in hops.
    pub hops: u8,
    /// When the path was learned.
    pub learned_at: Instant,
    /// When the path expires (policy from §3.6; configurable).
    pub expires_at: Instant,
}

/// The path table (§3.6).
#[derive(Default)]
pub struct PathTable {
    entries: HashMap<AddressHash, PathEntry>,
}

impl PathTable {
    /// Look up the path to a destination.
    pub fn lookup(&self, dst: &AddressHash) -> Option<&PathEntry> {
        self.entries.get(dst)
    }

    /// Insert or replace the path to a destination.
    pub fn insert(&mut self, entry: PathEntry) {
        self.entries.insert(entry.destination, entry);
    }

    /// Drop expired entries, returning how many were removed.
    pub fn evict_expired(&mut self, now: Instant) -> usize {
        let before = self.entries.len();
        self.entries.retain(|_, e| e.expires_at > now);
        before - self.entries.len()
    }

    /// The earliest expiry across all entries, if any (for deadline scheduling).
    pub fn earliest_expiry(&self) -> Option<Instant> {
        self.entries.values().map(|e| e.expires_at).min()
    }

    /// Number of entries.
    pub fn len(&self) -> usize {
        self.entries.len()
    }

    /// Whether the table is empty.
    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }
}
