//! Hydrant ATProto indexer setup and configuration.
//!
//! Initializes Hydrant from environment and applies exact Polymodel record
//! collection filters. Hydrant's by-collection crawler sends signal entries
//! directly to `listReposByCollection`, so wildcard NSIDs must not be used here.
//! The returned handle is cheaply cloneable; the firehose/backfill driver is started later via [`Hydrant::run`] in [`super::start_indexing`].

use hydrant::FilterMode;
use hydrant::control::Hydrant;
mod generated_collections {
    include!(concat!(env!("OUT_DIR"), "/polymodel_record_collections.rs"));
}
/// Exact authored Polymodel repo record collections used for Hydrant discovery
/// and storage. Keep this exact because Hydrant sends signal entries to listReposByCollection.
const INDEXED_COLLECTIONS: &[&str] = generated_collections::INDEXED_COLLECTIONS;

/// Initialize Hydrant from environment and configure signal-mode filtering.
///
/// Does NOT start the firehose. The caller drives [`Hydrant::run`] (started in
/// [`super::start_indexing`]) to begin ingest and backfill.
pub async fn init_hydrant() -> anyhow::Result<Hydrant> {
    let hydrant = Hydrant::from_env()
        .await
        .map_err(|e| anyhow::anyhow!("hydrant init failed: {e}"))?;

    // Hydrant signal mode discovers repos publishing authored Polymodel records.
    // In Hydrant's default ByCollection crawler, signals are also the exact
    // values sent to listReposByCollection, so do not use wildcard patterns.
    hydrant
        .filter
        .set_mode(FilterMode::Filter)
        .set_signals(INDEXED_COLLECTIONS.iter().copied())
        .set_collections(INDEXED_COLLECTIONS.iter().copied())
        .apply()
        .await
        .map_err(|e| anyhow::anyhow!("hydrant filter apply failed: {e}"))?;

    tracing::info!(
        indexed_collections = INDEXED_COLLECTIONS.len(),
        "hydrant initialized with exact authored record collection allowlist"
    );
    Ok(hydrant)
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn hydrant_filters_use_exact_record_collections() {
        assert_eq!(
            INDEXED_COLLECTIONS,
            generated_collections::INDEXED_COLLECTIONS
        );
        for collection in INDEXED_COLLECTIONS {
            assert!(
                !collection.contains('*'),
                "indexed collection must be exact, got {collection}"
            );
            assert!(
                !collection.ends_with(".*"),
                "indexed collection must not be a wildcard, got {collection}"
            );
        }
        let expected_records = [
            "space.polymodel.actor.profile",
            "space.polymodel.graph.follow",
            "space.polymodel.graph.like",
            "space.polymodel.graph.list",
            "space.polymodel.graph.listitem",
            "space.polymodel.graph.save",
            "space.polymodel.graph.tag",
            "space.polymodel.library.model",
            "space.polymodel.library.part",
            "space.polymodel.library.thing",
        ];
        for collection in expected_records {
            assert!(
                generated_collections::INDEXED_COLLECTIONS.contains(&collection),
                "missing generated record collection {collection}"
            );
        }
        let excluded_non_records = [
            "space.polymodel.library.getFeed",
            "space.polymodel.library.getThing",
            "space.polymodel.library.getAuthorThings",
            "space.polymodel.library.searchThings",
            "space.polymodel.graph.getList",
            "space.polymodel.library.defs",
        ];
        for collection in excluded_non_records {
            assert!(
                !generated_collections::INDEXED_COLLECTIONS.contains(&collection),
                "non-record lexicon must not be indexed as a collection: {collection}"
            );
        }
    }
}
