//! Storage helper methods for the IngestWorker.
//!
//! Contains methods for persisting cursors, writing supersession cascades,
//! and building storage keys.

use super::IngestWorker;
use crate::error::Result;
use stemedb_core::serde::deserialize;
use stemedb_core::types::Epoch;
use stemedb_storage::key_codec;
use stemedb_storage::KVStore;
use tracing::{debug, warn};

impl<S: KVStore + 'static> IngestWorker<S> {
    /// Maximum depth for walking supersession chains at write time.
    pub(super) const MAX_CASCADE_DEPTH: usize = 100;

    /// Write `\x00SUPERSEDED:` markers for the full transitive closure of superseded epochs.
    ///
    /// All markers point to the LATEST superseding epoch (`new_epoch_id`).
    /// For chain C→B→A: writes `SUPERSEDED:B→C` and `SUPERSEDED:A→C`.
    ///
    /// This enables O(1) "is this epoch superseded?" checks at query time:
    /// just look for `\x00SUPERSEDED:{epoch_id}` key existence.
    ///
    /// # Algorithm
    ///
    /// 1. Start with the immediately superseded epoch
    /// 2. Write marker pointing to the new (latest) epoch
    /// 3. Read the superseded epoch to check if it also supersedes something
    /// 4. Repeat transitively until end of chain or max depth
    ///
    /// # Safety
    ///
    /// - Cycle detection via visited set
    /// - Max depth guard (100 levels)
    /// - Missing/corrupt epochs gracefully terminate the walk
    pub(super) async fn write_supersession_cascade(
        &self,
        new_epoch_id: &[u8; 32],
        superseded_id: &[u8; 32],
    ) -> Result<()> {
        let mut current_id = *superseded_id;
        let mut visited = std::collections::HashSet::new();
        let mut depth = 0;

        loop {
            // Cycle detection
            if !visited.insert(current_id) {
                debug!(
                    epoch_id = %hex::encode(current_id),
                    "Cycle detected in supersession cascade, stopping write"
                );
                break;
            }

            // Max depth guard
            if depth >= Self::MAX_CASCADE_DEPTH {
                warn!(
                    depth,
                    new_epoch = %hex::encode(new_epoch_id),
                    "Supersession cascade exceeded max depth"
                );
                break;
            }

            // Write marker: \x00SUPERSEDED:{current_id} → new_epoch_id (always the LATEST)
            let marker_key = key_codec::superseded_key(&hex::encode(current_id));
            self.store.put(&marker_key, new_epoch_id).await?;

            debug!(
                superseded = %hex::encode(current_id),
                by = %hex::encode(new_epoch_id),
                depth,
                "Wrote supersession marker"
            );

            // Check if current_id also superseded something (transitive closure)
            let epoch_key = key_codec::epoch_key(&hex::encode(current_id));
            let ancestor_epoch = match self.store.get(&epoch_key).await? {
                Some(bytes) => match deserialize::<Epoch>(&bytes) {
                    Ok(e) => e,
                    Err(e) => {
                        debug!(
                            epoch_id = %hex::encode(current_id),
                            error = %e,
                            "Failed to deserialize ancestor epoch, stopping cascade"
                        );
                        break;
                    }
                },
                None => {
                    debug!(
                        epoch_id = %hex::encode(current_id),
                        "Ancestor epoch not found, stopping cascade"
                    );
                    break;
                }
            };

            match ancestor_epoch.supersedes {
                Some(grandparent_id) => {
                    current_id = grandparent_id;
                    depth += 1;
                }
                None => break, // End of chain
            }
        }

        Ok(())
    }
}
