use gloo_utils::format::JsValueSerdeExt;
use serde_json::Value;
use wasm_bindgen::{JsError, JsValue};

use crate::cspr_click::{
    callbacks::{ACCOUNT, CALLBACKS},
    types::WrappedAccountInfo
};

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum Event {
    SignedIn,
    Disconnected,
    SwitchAccount,
    UnsolicitedAccountChange,
    SignedOut,
    Loaded
}

impl Event {
    pub fn as_str(&self) -> &'static str {
        match self {
            Event::SignedIn => "csprclick:signed_in",
            Event::Disconnected => "csprclick:disconnected",
            Event::SwitchAccount => "csprclick:switched_account",
            Event::UnsolicitedAccountChange => "csprclick:unsolicited_account_change",
            Event::SignedOut => "csprclick:signed_out",
            Event::Loaded => "csprclick:loaded"
        }
    }

    pub fn closure(self) -> wasm_bindgen::closure::Closure<dyn FnMut(JsValue)> {
        wasm_bindgen::closure::Closure::<dyn FnMut(JsValue)>::new(move |evt: JsValue| {
            self.log(&evt);
            let account = evt.clone().into_serde::<WrappedAccountInfo>();
            if let Ok(account) = account {
                CALLBACKS.with(|callbacks| {
                    if let Some(cb) = callbacks.borrow().events.get(&self) {
                        if let Err(e) = cb.call1(&JsValue::NULL, &JsValue::from(account.account)) {
                            crate::js::log(&format!("Callback call failed: {:?}", e));
                        }
                        ACCOUNT.with(|account| {
                            *account.borrow_mut() = evt;
                        });
                    } else {
                        crate::js::log(&format!("No callback registered for event: {:?}", self));
                    }
                });
            } else {
                if self != Event::SignedOut {
                    ACCOUNT.with(|account| {
                        *account.borrow_mut() = JsValue::NULL;
                    });
                }
                CALLBACKS.with(|callbacks| {
                    if let Some(cb) = callbacks.borrow().events.get(&self) {
                        if let Err(e) = cb.call0(&JsValue::NULL) {
                            crate::js::log(&format!("Callback call failed: {:?}", e));
                        }
                    } else {
                        crate::js::log(&format!("No callback registered for event: {:?}", self));
                    }
                });
                crate::js::log(&format!("Failed to parse account info: {evt:?}"));
            }
        })
    }

    pub fn log(&self, data: &JsValue) {
        if let Ok(string) = Self::to_json_string(data) {
            crate::js::log(&format!("Event triggered: {}: {}", self.as_str(), string));
        } else {
            crate::js::log(&format!(
                "Event triggered: {}: [Failed to serialize data]",
                self.as_str()
            ));
        }
    }

    fn to_json_string(data: &JsValue) -> Result<String, JsError> {
        let serde_value: Value = data.into_serde()?;
        serde_json::to_string(&serde_value)
            .map_err(|e| JsError::new(&format!("Serialization error: {}", e)))
    }
}
