use anyhow::{Context, Result};
use rebecca::core::scan::ScanCancellationToken;

#[derive(Debug, Clone)]
pub(crate) struct CliRuntime {
    cancellation: ScanCancellationToken,
}

impl CliRuntime {
    #[cfg(test)]
    pub(crate) fn new(cancellation: ScanCancellationToken) -> Self {
        Self { cancellation }
    }

    pub(crate) fn child_task(&self) -> Self {
        Self {
            cancellation: self.cancellation.child_token(),
        }
    }

    pub(crate) fn with_ctrlc_handler() -> Result<Self> {
        let cancellation = ScanCancellationToken::new();
        ctrlc::set_handler({
            let cancellation = cancellation.clone();
            move || cancellation.cancel()
        })
        .context("failed to install Ctrl+C handler")?;

        Ok(Self { cancellation })
    }

    pub(crate) fn cancellation(&self) -> &ScanCancellationToken {
        &self.cancellation
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn runtime_exposes_shared_cancellation_token() {
        let token = ScanCancellationToken::new();
        let runtime = CliRuntime::new(token.clone());

        assert!(!runtime.cancellation().is_cancelled());

        token.cancel();

        assert!(runtime.cancellation().is_cancelled());
    }

    #[test]
    fn task_runtime_can_cancel_without_poisoning_parent() {
        let token = ScanCancellationToken::new();
        let runtime = CliRuntime::new(token.clone());
        let task_runtime = runtime.child_task();

        task_runtime.cancellation().cancel();

        assert!(task_runtime.cancellation().is_cancelled());
        assert!(!runtime.cancellation().is_cancelled());

        token.cancel();

        assert!(runtime.cancellation().is_cancelled());
    }
}
