//! Domain-Driven Design primitives for Soot applications.
//!
//! This module provides core traits and types that enforce DDD patterns:
//! - `Aggregate` - Marker trait for aggregate roots
//! - `Repository` - Persistence abstraction for aggregates
//! - `ValueObject` - Immutable, identity-less types
//! - `DomainEvent` - Events that occurred in the domain
//! - `EventPublisher` - Publishing domain events

mod event_bus;

pub use event_bus::{EventBus, InMemoryEventBus};

use async_trait::async_trait;
use chrono::{DateTime, Utc};
use std::fmt::Debug;
use std::hash::Hash;

/// Marker trait for aggregate roots - the consistency boundary in DDD.
///
/// An aggregate is a cluster of domain objects that can be treated as a single unit.
/// The aggregate root is the entry point to the aggregate and guarantees consistency.
///
/// # Example
///
/// ```ignore
/// use soot::ddd::Aggregate;
/// use uuid::Uuid;
///
/// struct User {
///     id: Uuid,
///     email: String,
///     name: String,
/// }
///
/// impl Aggregate for User {
///     type Id = Uuid;
///     fn id(&self) -> &Self::Id { &self.id }
/// }
/// ```
pub trait Aggregate: Sized + Send + Sync {
    /// The type used to identify this aggregate.
    type Id: Clone + Eq + Hash + Debug + Send + Sync;

    /// Returns a reference to this aggregate's identity.
    fn id(&self) -> &Self::Id;
}

/// Repository pattern - persistence abstraction for aggregates.
///
/// Repositories provide collection-like access to aggregates, hiding the
/// details of data storage. This allows the domain layer to remain pure
/// and enables easy testing with mock repositories.
///
/// # Example
///
/// ```ignore
/// use soot::ddd::{Aggregate, Repository};
/// use async_trait::async_trait;
///
/// #[async_trait]
/// impl Repository<User> for UserRepository {
///     type Error = sqlx::Error;
///
///     async fn find(&self, id: &Uuid) -> Result<Option<User>, Self::Error> {
///         // Query database...
///     }
///
///     async fn save(&self, aggregate: &User) -> Result<(), Self::Error> {
///         // Insert or update...
///     }
///
///     async fn delete(&self, id: &Uuid) -> Result<(), Self::Error> {
///         // Delete from database...
///     }
/// }
/// ```
#[async_trait]
pub trait Repository<A: Aggregate>: Send + Sync {
    /// The error type returned by repository operations.
    type Error: std::error::Error + Send + Sync + 'static;

    /// Find an aggregate by its identity.
    async fn find(&self, id: &A::Id) -> Result<Option<A>, Self::Error>;

    /// Save (insert or update) an aggregate.
    async fn save(&self, aggregate: &A) -> Result<(), Self::Error>;

    /// Delete an aggregate by its identity.
    async fn delete(&self, id: &A::Id) -> Result<(), Self::Error>;
}

/// Extension trait for repositories with listing capabilities.
#[async_trait]
pub trait ListableRepository<A: Aggregate>: Repository<A> {
    /// List all aggregates, with optional pagination.
    async fn list(&self, limit: Option<i64>, offset: Option<i64>) -> Result<Vec<A>, Self::Error>;

    /// Count total aggregates.
    async fn count(&self) -> Result<i64, Self::Error>;
}

/// Value objects - immutable, identity-less, compared by value.
///
/// Value objects represent descriptive aspects of the domain with no conceptual identity.
/// They are defined by their attributes rather than an ID.
///
/// # Example
///
/// ```ignore
/// use soot::ddd::ValueObject;
///
/// #[derive(Clone, Debug, PartialEq, Eq)]
/// struct Email {
///     address: String,
/// }
///
/// impl ValueObject for Email {}
/// ```
pub trait ValueObject: Clone + Eq + PartialEq + Debug + Send + Sync {}

/// Domain events - things that happened in the domain.
///
/// Domain events capture facts about what happened in your domain.
/// They are immutable and named in past tense (e.g., `UserCreated`, `OrderShipped`).
///
/// # Example
///
/// ```ignore
/// use soot::ddd::DomainEvent;
/// use chrono::{DateTime, Utc};
///
/// #[derive(Clone, Debug)]
/// struct UserCreated {
///     user_id: Uuid,
///     email: String,
///     occurred_at: DateTime<Utc>,
/// }
///
/// impl DomainEvent for UserCreated {
///     fn event_type(&self) -> &'static str { "user.created" }
///     fn occurred_at(&self) -> DateTime<Utc> { self.occurred_at }
/// }
/// ```
pub trait DomainEvent: Clone + Debug + Send + Sync + 'static {
    /// Returns the event type identifier (e.g., "user.created").
    fn event_type(&self) -> &'static str;

    /// Returns when this event occurred.
    fn occurred_at(&self) -> DateTime<Utc>;
}

/// Event publisher for domain events.
///
/// Allows publishing domain events to be handled by subscribers.
/// This enables loose coupling between bounded contexts.
#[async_trait]
pub trait EventPublisher: Send + Sync {
    /// Publish a domain event.
    async fn publish<E: DomainEvent>(&self, event: E);
}

/// Unit of Work - transaction boundary.
///
/// Groups multiple repository operations into a single transaction.
#[async_trait]
pub trait UnitOfWork: Send {
    /// The error type for transaction operations.
    type Error: std::error::Error + Send + Sync + 'static;

    /// Commit all changes in the unit of work.
    async fn commit(self) -> Result<(), Self::Error>;

    /// Rollback all changes in the unit of work.
    async fn rollback(self) -> Result<(), Self::Error>;
}

/// A type alias for aggregate IDs using UUID.
pub type AggregateId = uuid::Uuid;

/// Generate a new aggregate ID.
#[must_use]
pub fn new_aggregate_id() -> AggregateId {
    uuid::Uuid::new_v4()
}
