//! CRUD event runtime for the generated Nautilus Rust client.
//!
//! This module is generated by nautilus-codegen. Do not edit manually.

use std::any::Any;
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, RwLock};

pub type EventFuture<'a, T> =
    Pin<Box<dyn Future<Output = nautilus_core::Result<EventControl<T>>> + Send + 'a>>;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EventPhase {
    Before,
    After,
    Error,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CrudOperation {
    Create,
    CreateMany,
    Update,
    Delete,
    DeleteMany,
}

#[derive(Debug, Clone, PartialEq)]
pub enum EventControl<T> {
    Continue,
    StopPropagation(T),
}

pub trait IntoEventResult<T> {
    fn into_event_result(self) -> nautilus_core::Result<EventControl<T>>;
}

impl<T> IntoEventResult<T> for () {
    fn into_event_result(self) -> nautilus_core::Result<EventControl<T>> {
        Ok(EventControl::Continue)
    }
}

impl<T> IntoEventResult<T> for EventControl<T> {
    fn into_event_result(self) -> nautilus_core::Result<EventControl<T>> {
        Ok(self)
    }
}

impl<T> IntoEventResult<T> for nautilus_core::Result<()> {
    fn into_event_result(self) -> nautilus_core::Result<EventControl<T>> {
        self.map(|()| EventControl::Continue)
    }
}

impl<T> IntoEventResult<T> for nautilus_core::Result<EventControl<T>> {
    fn into_event_result(self) -> nautilus_core::Result<EventControl<T>> {
        self
    }
}

#[derive(Debug, Clone)]
pub struct CrudEventContext<TArgs, TResult> {
    pub model_name: &'static str,
    pub operation: CrudOperation,
    pub phase: EventPhase,
    pub args: TArgs,
    pub payload: serde_json::Value,
    pub result: Option<TResult>,
    pub error: Option<String>,
    pub transaction_id: Option<String>,
    pub state: HashMap<String, serde_json::Value>,
}

impl<TArgs, TResult> CrudEventContext<TArgs, TResult> {
    pub fn new(
        model_name: &'static str,
        operation: CrudOperation,
        phase: EventPhase,
        args: TArgs,
        payload: serde_json::Value,
        state: HashMap<String, serde_json::Value>,
    ) -> Self {
        Self {
            model_name,
            operation,
            phase,
            args,
            payload,
            result: None,
            error: None,
            transaction_id: None,
            state,
        }
    }

    pub fn with_result(mut self, result: TResult) -> Self {
        self.result = Some(result);
        self
    }

    pub fn with_error(mut self, error: impl Into<String>) -> Self {
        self.error = Some(error.into());
        self
    }

    pub fn with_transaction_id(mut self, transaction_id: Option<String>) -> Self {
        self.transaction_id = transaction_id;
        self
    }
}

type RawEventFuture<'a> =
    Pin<Box<dyn Future<Output = nautilus_core::Result<RawEventControl>> + Send + 'a>>;

enum RawEventControl {
    Continue,
    StopPropagation(Box<dyn Any + Send>),
}

type RawEventHandler =
    Arc<dyn for<'a> Fn(&'a mut dyn Any) -> RawEventFuture<'a> + Send + Sync + 'static>;

#[derive(Clone)]
struct EventHandlerEntry {
    priority: u8,
    handler: RawEventHandler,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct EventKey {
    model_name: &'static str,
    operation: CrudOperation,
    phase: EventPhase,
}

#[derive(Clone, Default)]
pub struct EventRegistry {
    handlers: Arc<RwLock<HashMap<EventKey, Vec<EventHandlerEntry>>>>,
}

impl EventRegistry {
    pub fn on_create<C, T, F>(&self, model_name: &'static str, phase: EventPhase, handler: F)
    where
        C: Any + Send + 'static,
        T: Any + Send + 'static,
        F: for<'a> Fn(&'a mut C) -> EventFuture<'a, T> + Send + Sync + 'static,
    {
        self.register::<C, T, F>(model_name, CrudOperation::Create, phase, 0, handler);
    }

    pub fn on_create_with_priority<C, T, F>(
        &self,
        model_name: &'static str,
        phase: EventPhase,
        priority: u8,
        handler: F,
    ) where
        C: Any + Send + 'static,
        T: Any + Send + 'static,
        F: for<'a> Fn(&'a mut C) -> EventFuture<'a, T> + Send + Sync + 'static,
    {
        self.register::<C, T, F>(model_name, CrudOperation::Create, phase, priority, handler);
    }

    pub fn on_create_many<C, T, F>(&self, model_name: &'static str, phase: EventPhase, handler: F)
    where
        C: Any + Send + 'static,
        T: Any + Send + 'static,
        F: for<'a> Fn(&'a mut C) -> EventFuture<'a, T> + Send + Sync + 'static,
    {
        self.register::<C, T, F>(model_name, CrudOperation::CreateMany, phase, 0, handler);
    }

    pub fn on_create_many_with_priority<C, T, F>(
        &self,
        model_name: &'static str,
        phase: EventPhase,
        priority: u8,
        handler: F,
    ) where
        C: Any + Send + 'static,
        T: Any + Send + 'static,
        F: for<'a> Fn(&'a mut C) -> EventFuture<'a, T> + Send + Sync + 'static,
    {
        self.register::<C, T, F>(
            model_name,
            CrudOperation::CreateMany,
            phase,
            priority,
            handler,
        );
    }

    pub fn on_update<C, T, F>(&self, model_name: &'static str, phase: EventPhase, handler: F)
    where
        C: Any + Send + 'static,
        T: Any + Send + 'static,
        F: for<'a> Fn(&'a mut C) -> EventFuture<'a, T> + Send + Sync + 'static,
    {
        self.register::<C, T, F>(model_name, CrudOperation::Update, phase, 0, handler);
    }

    pub fn on_update_with_priority<C, T, F>(
        &self,
        model_name: &'static str,
        phase: EventPhase,
        priority: u8,
        handler: F,
    ) where
        C: Any + Send + 'static,
        T: Any + Send + 'static,
        F: for<'a> Fn(&'a mut C) -> EventFuture<'a, T> + Send + Sync + 'static,
    {
        self.register::<C, T, F>(model_name, CrudOperation::Update, phase, priority, handler);
    }

    pub fn on_delete<C, T, F>(&self, model_name: &'static str, phase: EventPhase, handler: F)
    where
        C: Any + Send + 'static,
        T: Any + Send + 'static,
        F: for<'a> Fn(&'a mut C) -> EventFuture<'a, T> + Send + Sync + 'static,
    {
        self.register::<C, T, F>(model_name, CrudOperation::Delete, phase, 0, handler);
    }

    pub fn on_delete_with_priority<C, T, F>(
        &self,
        model_name: &'static str,
        phase: EventPhase,
        priority: u8,
        handler: F,
    ) where
        C: Any + Send + 'static,
        T: Any + Send + 'static,
        F: for<'a> Fn(&'a mut C) -> EventFuture<'a, T> + Send + Sync + 'static,
    {
        self.register::<C, T, F>(model_name, CrudOperation::Delete, phase, priority, handler);
    }

    pub fn on_delete_many<C, T, F>(&self, model_name: &'static str, phase: EventPhase, handler: F)
    where
        C: Any + Send + 'static,
        T: Any + Send + 'static,
        F: for<'a> Fn(&'a mut C) -> EventFuture<'a, T> + Send + Sync + 'static,
    {
        self.register::<C, T, F>(model_name, CrudOperation::DeleteMany, phase, 0, handler);
    }

    pub fn on_delete_many_with_priority<C, T, F>(
        &self,
        model_name: &'static str,
        phase: EventPhase,
        priority: u8,
        handler: F,
    ) where
        C: Any + Send + 'static,
        T: Any + Send + 'static,
        F: for<'a> Fn(&'a mut C) -> EventFuture<'a, T> + Send + Sync + 'static,
    {
        self.register::<C, T, F>(
            model_name,
            CrudOperation::DeleteMany,
            phase,
            priority,
            handler,
        );
    }

    fn register<C, T, F>(
        &self,
        model_name: &'static str,
        operation: CrudOperation,
        phase: EventPhase,
        priority: u8,
        handler: F,
    ) where
        C: Any + Send + 'static,
        T: Any + Send + 'static,
        F: for<'a> Fn(&'a mut C) -> EventFuture<'a, T> + Send + Sync + 'static,
    {
        let wrapped: RawEventHandler = Arc::new(move |raw_context| {
            let Some(context) = raw_context.downcast_mut::<C>() else {
                return Box::pin(async {
                    Err(nautilus_core::Error::TypeError(
                        "CRUD event context type mismatch".to_string(),
                    ))
                });
            };

            let future = handler(context);
            Box::pin(async move {
                match future.await? {
                    EventControl::Continue => Ok(RawEventControl::Continue),
                    EventControl::StopPropagation(result) => {
                        Ok(RawEventControl::StopPropagation(Box::new(result)))
                    }
                }
            })
        });

        let key = EventKey {
            model_name,
            operation,
            phase,
        };
        let mut guard = self.handlers.write().expect("event registry lock poisoned");
        let handlers = guard.entry(key).or_default();
        handlers.push(EventHandlerEntry {
            priority,
            handler: wrapped,
        });
        handlers.sort_by(|left, right| right.priority.cmp(&left.priority));
    }

    pub async fn run<C, T>(
        &self,
        model_name: &'static str,
        operation: CrudOperation,
        phase: EventPhase,
        context: &mut C,
    ) -> nautilus_core::Result<Option<T>>
    where
        C: Any + Send + 'static,
        T: Any + Send + 'static,
    {
        let key = EventKey {
            model_name,
            operation,
            phase,
        };

        let handlers = {
            let guard = self.handlers.read().expect("event registry lock poisoned");
            guard.get(&key).cloned()
        };

        let Some(handlers) = handlers else {
            return Ok(None);
        };

        for entry in handlers {
            match (entry.handler)(context).await? {
                RawEventControl::Continue => {}
                RawEventControl::StopPropagation(result) => {
                    let result = result.downcast::<T>().map_err(|_| {
                        nautilus_core::Error::TypeError(
                            "CRUD event stop result type mismatch".to_string(),
                        )
                    })?;
                    return Ok(Some(*result));
                }
            }
        }

        Ok(None)
    }
}
