updt 1.0.2

Cross-platform update helper for npm, cargo, rustup, fnm, scoop, Homebrew, paru, flatpak, pacman, and pkg.
use std::future::Future;

use tokio::sync::mpsc::UnboundedSender;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OutputStream {
    Stdout,
    Stderr,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum UpgradeEvent {
    TargetStarted { target: String },
    TargetFinished { target: String, success: bool },
    CommandStarted { program: String, args: Vec<String> },
    Output { stream: OutputStream, text: String },
    CommandFinished { success: bool, code: i32 },
    Message { text: String },
    HandoffStarted { label: String },
    HandoffFinished { success: bool },
    Finished { success: bool },
}

#[derive(Clone, Default)]
pub struct UpgradeSink {
    sender: Option<UnboundedSender<UpgradeEvent>>,
}

impl UpgradeSink {
    pub fn new(sender: UnboundedSender<UpgradeEvent>) -> Self {
        Self {
            sender: Some(sender),
        }
    }

    fn emit(&self, event: UpgradeEvent) -> bool {
        self.sender
            .as_ref()
            .is_some_and(|sender| sender.send(event).is_ok())
    }
}

tokio::task_local! {
    static ACTIVE_UPGRADE_SINK: UpgradeSink;
}

pub async fn with_upgrade_sink<F, T>(sender: Option<UnboundedSender<UpgradeEvent>>, future: F) -> T
where
    F: Future<Output = T>,
{
    let sink = sender.map(UpgradeSink::new).unwrap_or_default();
    ACTIVE_UPGRADE_SINK.scope(sink, future).await
}

tokio::task_local! {
    static TUI_ACTIVE: ();
}

pub async fn with_tui_active<F, T>(future: F) -> T
where
    F: Future<Output = T>,
{
    TUI_ACTIVE.scope((), future).await
}

pub fn tui_active() -> bool {
    TUI_ACTIVE.try_with(|_| ()).is_ok()
}

pub fn upgrade_events_enabled() -> bool {
    ACTIVE_UPGRADE_SINK
        .try_with(|sink| sink.sender.is_some())
        .unwrap_or(false)
}

pub fn emit_upgrade_event(event: UpgradeEvent) -> bool {
    ACTIVE_UPGRADE_SINK
        .try_with(|sink| sink.emit(event))
        .unwrap_or(false)
}

pub fn log_upgrade_message(text: impl Into<String>) {
    let text = text.into();
    if !emit_upgrade_event(UpgradeEvent::Message { text: text.clone() }) {
        println!("{text}");
    }
}