//! Cohort management operations on `TidalDb`.

use super::TidalDb;
use crate::{
    cohort::serialize_cohort_def,
    schema::EntityId,
    storage::{Tag, encode_key},
};

impl TidalDb {
    /// Register a cohort definition.
    ///
    /// Persists the definition to durable storage (fjall) so it survives
    /// restarts. In ephemeral mode, the definition is only held in memory.
    ///
    /// # Errors
    ///
    /// Returns `TidalError::Internal` if a cohort with the same name already exists.
    pub fn define_cohort(&self, def: crate::cohort::CohortDef) -> crate::Result<()> {
        self.require_writeable("define_cohort")?;
        // Persist to durable storage before registering in-memory, so a crash
        // between the two leaves the definition on disk for the next open.
        //
        // Persist failure is intentionally non-fatal here: the in-memory
        // registry remains authoritative for this process lifetime, and a
        // failed durable write is surfaced as a warning rather than aborting the
        // definition. (This is the one persist caller whose contract is
        // warn-and-continue rather than propagate.)
        let key = encode_key(EntityId::new(0), Tag::CohortDef, def.name.as_bytes());
        // A serialization failure is (today) impossible for these types, but if a
        // future Predicate variant makes it possible we fail loudly rather than
        // persist an empty Vec that silently drops the definition on restart.
        let value = serialize_cohort_def(&def)?;
        if let Err(e) = self.persist_to_items(&key, &value, "define_cohort") {
            tracing::warn!(
                error = %e,
                name = %def.name,
                "failed to persist cohort definition"
            );
        }

        self.cohort_registry.define(def)?;

        // The resolver caches per-user memberships computed against the cohort
        // set as it was when each user was last resolved. A newly-defined cohort
        // changes that set for everyone, so every cached membership is now stale
        // and would silently omit this cohort — its ledger would accumulate zero
        // signal for any already-resolved user until they were LRU-evicted or
        // their metadata was rewritten. Clear the whole cache so the next signal
        // for each user re-resolves against the new registry. We do this only
        // after `define` succeeds, so a rejected definition (e.g. duplicate
        // name) does not needlessly flush the accelerator. The clear is cheap:
        // the cache is a pure accelerator and recomputes every entry on miss.
        self.cohort_resolver.invalidate_all();

        Ok(())
    }
}
