//! Saga - The Write-Ahead Log (WAL) for HA replication.
//!
//! Named after the Nordic goddess of history and storytelling, Saga
//! records every change to the well for replication across nodes.

use mimirs_core::{AgentId, BlockId, Memory, MemoryBlock, MimirError};
use serde::{Deserialize, Serialize};

/// SagaEntry - A single entry in the write-ahead log.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SagaEntry {
    /// Memory carved into the well.
    Rista(Memory),
    /// Memories linked via relationship.
    Tengja(String, String, String),
    /// Block attached to agent for cross-node persistence.
    BlockAttached {
        /// Identifier of the agent owning the block.
        agent_id: AgentId,
        /// The memory block being attached.
        block: MemoryBlock,
    },
    /// Block detached from agent.
    BlockDetached {
        /// Identifier of the agent.
        agent_id: AgentId,
        /// Identifier of the block being detached.
        block_id: BlockId,
    },
}

/// Trait for items that can apply Saga log entries.
pub trait ApplyChange {
    /// Applies a SagaEntry to the current state.
    fn apply(&self, entry: SagaEntry) -> Result<(), MimirError>;
}

/// Saga - The Write-Ahead Log.
pub struct Saga {
    /// Local log sequence number (LSN)
    pub lsn: u64,
    /// In-memory buffer of log entries.
    entries: Vec<(u64, SagaEntry)>,
}

impl Saga {
    /// Opna - Creates a new Saga log.
    pub fn new() -> Self {
        Self {
            lsn: 0,
            entries: Vec::new(),
        }
    }

    /// Skrifa - Write an entry to the log.
    pub fn skrifa(&mut self, entry: SagaEntry) -> Result<u64, MimirError> {
        self.lsn += 1;
        self.entries.push((self.lsn, entry));

        // debug_assert! verifies LSN monotonicity
        debug_assert!(
            self.entries.len() < 2
                || self.entries[self.entries.len() - 1].0 > self.entries[self.entries.len() - 2].0
        );

        // NOTE: In-memory log rotation is disabled to prevent follower sync gaps.
        // Truncation should only happen after global consensus snapshots.
        /*
        if self.entries.len() > 10000 {
            self.entries.drain(0..1000);
        }
        */

        Ok(self.lsn)
    }

    /// Lesa - Read entries from the log starting at a given LSN.
    pub fn lesa(&self, start_lsn: u64) -> Vec<(u64, SagaEntry)> {
        self.entries
            .iter()
            .filter(|(lsn, _)| *lsn >= start_lsn)
            .cloned()
            .collect()
    }

    /// Clear the log up to a given LSN.
    pub fn clear_up_to(&mut self, lsn: u64) {
        self.entries.retain(|(e_lsn, _)| *e_lsn > lsn);
    }

    /// Applies log entries from the specified LSN to the given state.
    pub fn apply_to(
        &self,
        state: &dyn ApplyChange,
        from_lsn: u64,
    ) -> Result<(), mimirs_core::MimirError> {
        for (lsn, entry) in &self.entries {
            if *lsn >= from_lsn {
                state.apply(entry.clone())?;
            }
        }
        Ok(())
    }
}

impl Default for Saga {
    fn default() -> Self {
        Self::new()
    }
}
