//! Maps a `StreamConfig`'s `type` string to a concrete [`SessionStream`].
//!
//! Stream implementations register themselves with a
//! [`StreamFactoryRegistry`] at boot so the runtime can construct streams
//! from `[[streams]]` config blocks without knowing every concrete type up
//! front. This is the stream-side analogue of the archivist's
//! `BackendRegistry` / `BackendFactory` pattern (Phase 3).

use std::collections::HashMap;
use std::sync::Arc;

use async_trait::async_trait;
use thiserror::Error;

use dirigent_protocol::streaming::SessionStream;

use super::config::StreamConfig;

/// Builds a concrete [`SessionStream`] from a `StreamConfig`.
///
/// Each implementation advertises a single `kind` (matching the `type`
/// string in the TOML config). The registry routes config blocks to the
/// matching factory at boot.
#[async_trait]
pub trait StreamFactory: Send + Sync {
    /// The `type` discriminator in `[[streams]]` — e.g. `"matrix"`,
    /// `"langfuse"`. Must be unique across all registered factories.
    fn kind(&self) -> &'static str;

    /// Build a running stream from its config. Implementations are
    /// expected to read `cfg.params` (type-specific TOML table), establish
    /// any transport, and return an [`Arc<dyn SessionStream>`] ready to
    /// receive events.
    async fn build(&self, cfg: &StreamConfig) -> Result<Arc<dyn SessionStream>, StreamBuildError>;
}

/// Lookup table of `StreamFactory` implementations keyed by `kind()`.
///
/// Populate this at startup (typically once, on the main thread) before
/// handing it off to the runtime. The registry is cheap to clone via
/// `Arc` at the call site.
#[derive(Default)]
pub struct StreamFactoryRegistry {
    factories: HashMap<&'static str, Arc<dyn StreamFactory>>,
}

impl std::fmt::Debug for StreamFactoryRegistry {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("StreamFactoryRegistry")
            .field("kinds", &self.factories.keys().collect::<Vec<_>>())
            .finish()
    }
}

impl StreamFactoryRegistry {
    /// Construct an empty registry.
    pub fn new() -> Self {
        Self::default()
    }

    /// Register a factory. Consumes and returns `self` so callers can
    /// chain `.register(...)` calls at startup.
    pub fn register<F: StreamFactory + 'static>(mut self, f: F) -> Self {
        self.factories.insert(f.kind(), Arc::new(f));
        self
    }

    /// Look up a factory by its `kind` discriminator, or `None` if no
    /// factory for that kind has been registered.
    pub fn get(&self, kind: &str) -> Option<&Arc<dyn StreamFactory>> {
        self.factories.get(kind)
    }
}

/// Errors raised while building a `SessionStream` from its config.
#[derive(Debug, Error)]
pub enum StreamBuildError {
    /// No factory is registered for `cfg.kind`.
    #[error("unknown kind: {0}")]
    UnknownKind(String),
    /// Config was structurally valid TOML but semantically invalid
    /// (missing required field, enum out of range, etc).
    #[error("config: {0}")]
    Config(String),
    /// The factory accepted the config but the transport refused to come
    /// up (network error, auth failure, …).
    #[error("transport: {0}")]
    Transport(String),
}

// ─── Tests ───────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use async_trait::async_trait;
    use dirigent_protocol::streaming::{
        BusEvent, SessionStream, StreamKind, StreamOutcome, StreamScope, StreamSummary,
    };

    struct DummyStream;

    #[async_trait]
    impl SessionStream for DummyStream {
        fn summary(&self) -> StreamSummary {
            StreamSummary {
                name: "dummy".to_string(),
                kind: StreamKind::Custom,
                target: "dummy".to_string(),
                active_since: chrono::Utc::now(),
            }
        }
        fn scope(&self) -> StreamScope {
            StreamScope::ArchiveWide { acknowledged: false }
        }
        async fn on_event(&self, _event: &BusEvent) -> StreamOutcome {
            StreamOutcome::Ok
        }
        async fn shutdown(&self) {}
    }

    struct DummyFactory;

    #[async_trait]
    impl StreamFactory for DummyFactory {
        fn kind(&self) -> &'static str {
            "dummy"
        }
        async fn build(
            &self,
            _cfg: &StreamConfig,
        ) -> Result<Arc<dyn SessionStream>, StreamBuildError> {
            Ok(Arc::new(DummyStream))
        }
    }

    #[test]
    fn register_and_lookup() {
        let reg = StreamFactoryRegistry::new().register(DummyFactory);
        assert!(reg.get("dummy").is_some());
        assert!(reg.get("missing").is_none());
    }
}
