//! Rust side SDK for writing B站弹幕姬 (`bililive_dm`) plugins through the .NET bridge.

pub mod ffi;
pub mod host;
pub mod model;

pub use host::Host;
pub use model::{
    Danmaku, DisconnectEvent, GiftRank, InteractType, MsgType, PluginContext, PluginMetadata,
};

pub trait DanmujiPlugin: Send + 'static {
    fn metadata(&self) -> PluginMetadata;

    fn inited(&mut self, _host: Host, _ctx: PluginContext) {}

    fn start(&mut self, _host: Host, _ctx: PluginContext) {}

    fn stop(&mut self, _host: Host, _ctx: PluginContext) {}

    fn admin(&mut self, _host: Host, _ctx: PluginContext) {}

    fn deinit(&mut self, _host: Host, _ctx: PluginContext) {}

    fn connected(&mut self, _host: Host, _room_id: i32) {}

    fn disconnected(&mut self, _host: Host, _event: DisconnectEvent) {}

    fn room_count(&mut self, _host: Host, _user_count: u32) {}

    fn danmaku(&mut self, _host: Host, _danmaku: Danmaku) {}
}

#[macro_export]
macro_rules! export_plugin {
    ($plugin:expr) => {
        static DANMUJI_RS_PLUGIN: ::std::sync::OnceLock<
            ::std::sync::Mutex<Box<dyn $crate::DanmujiPlugin>>,
        > = ::std::sync::OnceLock::new();

        fn danmuji_rs_plugin_instance(
        ) -> &'static ::std::sync::Mutex<Box<dyn $crate::DanmujiPlugin>> {
            DANMUJI_RS_PLUGIN.get_or_init(|| ::std::sync::Mutex::new(Box::new($plugin)))
        }

        fn danmuji_rs_with_plugin<F>(f: F)
        where
            F: FnOnce(&mut dyn $crate::DanmujiPlugin),
        {
            let result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
                let instance = danmuji_rs_plugin_instance();
                match instance.lock() {
                    Ok(mut plugin) => f(plugin.as_mut()),
                    Err(poisoned) => {
                        let mut plugin = poisoned.into_inner();
                        f(plugin.as_mut());
                    }
                }
            }));

            if result.is_err() {
                $crate::Host::new().log("Rust plugin panic was caught by danmuji-sdk");
            }
        }

        #[doc = "# Safety\n\n`out` must be non-null and point to writable memory for one `FfiPluginMetadata`."]
        #[no_mangle]
        pub unsafe extern "C" fn danmuji_rs_plugin_metadata(
            out: *mut $crate::ffi::FfiPluginMetadata,
        ) {
            if out.is_null() {
                return;
            }

            let metadata = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
                let instance = danmuji_rs_plugin_instance();
                match instance.lock() {
                    Ok(plugin) => plugin.metadata(),
                    Err(poisoned) => poisoned.into_inner().metadata(),
                }
            }))
            .unwrap_or($crate::PluginMetadata {
                name: "Rust Plugin",
                author: "",
                contact: "",
                version: "",
                description: "Rust plugin metadata failed",
            });

            unsafe {
                *out = metadata.into_ffi();
            }
        }

        #[no_mangle]
        pub extern "C" fn danmuji_rs_plugin_set_host(api: $crate::ffi::FfiHostApi) {
            $crate::ffi::set_host_api(api);
        }

        #[no_mangle]
        pub extern "C" fn danmuji_rs_plugin_inited(ctx: $crate::ffi::FfiPluginContext) {
            danmuji_rs_with_plugin(|plugin| plugin.inited($crate::Host::new(), ctx.into()));
        }

        #[no_mangle]
        pub extern "C" fn danmuji_rs_plugin_start(ctx: $crate::ffi::FfiPluginContext) {
            danmuji_rs_with_plugin(|plugin| plugin.start($crate::Host::new(), ctx.into()));
        }

        #[no_mangle]
        pub extern "C" fn danmuji_rs_plugin_stop(ctx: $crate::ffi::FfiPluginContext) {
            danmuji_rs_with_plugin(|plugin| plugin.stop($crate::Host::new(), ctx.into()));
        }

        #[no_mangle]
        pub extern "C" fn danmuji_rs_plugin_admin(ctx: $crate::ffi::FfiPluginContext) {
            danmuji_rs_with_plugin(|plugin| plugin.admin($crate::Host::new(), ctx.into()));
        }

        #[no_mangle]
        pub extern "C" fn danmuji_rs_plugin_deinit(ctx: $crate::ffi::FfiPluginContext) {
            danmuji_rs_with_plugin(|plugin| plugin.deinit($crate::Host::new(), ctx.into()));
        }

        #[no_mangle]
        pub extern "C" fn danmuji_rs_plugin_on_connected(room_id: i32) {
            danmuji_rs_with_plugin(|plugin| plugin.connected($crate::Host::new(), room_id));
        }

        #[no_mangle]
        pub extern "C" fn danmuji_rs_plugin_on_disconnected(error: $crate::ffi::FfiStr) {
            let event = $crate::DisconnectEvent {
                error: unsafe { error.to_string_lossy() },
            };
            danmuji_rs_with_plugin(|plugin| plugin.disconnected($crate::Host::new(), event));
        }

        #[no_mangle]
        pub extern "C" fn danmuji_rs_plugin_on_room_count(user_count: u32) {
            danmuji_rs_with_plugin(|plugin| plugin.room_count($crate::Host::new(), user_count));
        }

        #[doc = "# Safety\n\n`danmaku` must be non-null and point to a readable `FfiDanmaku` value."]
        #[no_mangle]
        pub unsafe extern "C" fn danmuji_rs_plugin_on_danmaku(
            danmaku: *const $crate::ffi::FfiDanmaku,
        ) {
            if danmaku.is_null() {
                return;
            }

            let danmaku = unsafe { (*danmaku).to_model() };
            danmuji_rs_with_plugin(|plugin| plugin.danmaku($crate::Host::new(), danmaku));
        }
    };
}
