#[cfg(feature = "ipc")]
use crate::Ironbar;
use crate::channels::{AsyncSenderExt, Dependency, MpscReceiverExt};
use crate::script::Script;
use crate::spawn;
use cfg_if::cfg_if;
use serde::Deserialize;
use tokio::sync::mpsc;

#[derive(Debug, Deserialize, Clone)]
#[serde(untagged)]
#[cfg_attr(feature = "extras", derive(schemars::JsonSchema))]
pub enum DynamicBool {
    /// Either a script or variable, to be determined.
    Unknown(String),
    Script(Script),
    #[cfg(feature = "ipc")]
    Variable(Box<str>),
}

impl DynamicBool {
    pub fn subscribe<D, F>(self, deps: D, f: F)
    where
        D: Dependency,
        D::Target: Clone + 'static,
        F: FnMut(&D::Target, bool) + 'static,
    {
        let value = match self {
            Self::Unknown(input) => {
                if input.starts_with('#') {
                    cfg_if! {
                        if #[cfg(feature = "ipc")] {
                            Self::Variable(input.into())
                        } else {
                            Self::Unknown(input)
                        }
                    }
                } else {
                    let script = Script::from(input.as_str());
                    Self::Script(script)
                }
            }
            _ => self,
        };

        let (tx, rx) = mpsc::channel(32);

        rx.recv_glib(deps, f);

        spawn(async move {
            match value {
                DynamicBool::Script(script) => {
                    script
                        .run(None, |_, success| {
                            tx.send_spawn(success);
                        })
                        .await;
                }
                #[cfg(feature = "ipc")]
                DynamicBool::Variable(variable) => {
                    let variable_manager = Ironbar::variable_manager();

                    let variable_name = variable[1..].into(); // remove hash
                    let mut rx = variable_manager.subscribe(variable_name);

                    while let Ok(value) = rx.recv().await {
                        let has_value = value.is_some_and(|s| is_truthy(&s));
                        tx.send_expect(has_value).await;
                    }
                }
                DynamicBool::Unknown(_) => unreachable!(),
            }
        });
    }
}

/// Check if a string ironvar is 'truthy',
/// i.e should be evaluated to true.
///
/// This loosely follows the common JavaScript cases.
#[cfg(feature = "ipc")]
fn is_truthy(string: &str) -> bool {
    !(string.is_empty() || string == "0" || string == "false")
}
