use indicatif::MultiProgress;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, OnceLock};

use crate::error::{Result, OilError};

static SHUTDOWN_REQUESTED: AtomicBool = AtomicBool::new(false);
static CRITICAL_SECTION: AtomicBool = AtomicBool::new(false);

static CURRENT_OP: OnceLock<Mutex<String>> = OnceLock::new();
static ACTIVE_MULTI: OnceLock<Mutex<Option<MultiProgress>>> = OnceLock::new();

fn active_multi_mutex() -> &'static Mutex<Option<MultiProgress>> {
    ACTIVE_MULTI.get_or_init(|| Mutex::new(None))
}

pub fn set_active_multi(multi: MultiProgress) {
    if let Ok(mut guard) = active_multi_mutex().lock() {
        *guard = Some(multi);
    }
}

pub fn clear_active_multi() {
    if let Ok(mut guard) = active_multi_mutex().lock() {
        *guard = None;
    }
}

/// Clone the currently active MultiProgress, if any.
/// Callers that need to add bars or print through the active render layer
/// (e.g. install_casks called from upgrade) use this to avoid spawning a
/// competing MultiProgress instance that causes terminal tearing.
pub fn clone_active_multi() -> Option<MultiProgress> {
    active_multi_mutex()
        .lock()
        .ok()
        .and_then(|guard| guard.as_ref().cloned())
}

/// Run `f` while indicatif progress rendering is paused so interactive prompts
/// (e.g. `sudo` password) are visible on the terminal.
pub fn with_suspended_progress<F, R>(f: F) -> R
where
    F: FnOnce() -> R,
{
    if let Some(m) = clone_active_multi() {
        m.suspend(f)
    } else {
        f()
    }
}

/// Print a line through the active multi-progress layer when one is registered
/// (e.g. cask preflight notes). Falls back to `println!` otherwise.
pub fn println_through_active_multi(msg: impl Into<String>) {
    let s = msg.into();
    if let Some(m) = clone_active_multi() {
        let _ = m.println(s);
    } else {
        println!("{s}");
    }
}

/// Print a message that appears correctly above/alongside active progress bars.
fn print_interrupt(msg: &str) {
    let used_multi = active_multi_mutex()
        .lock()
        .ok()
        .and_then(|guard| guard.as_ref().map(|m| m.println(msg).is_ok()))
        .unwrap_or(false);
    if !used_multi {
        eprintln!("{}", msg);
    }
}

fn current_op_mutex() -> &'static Mutex<String> {
    CURRENT_OP.get_or_init(|| Mutex::new(String::new()))
}

pub fn set_current_op(op: impl Into<String>) {
    if let Ok(mut guard) = current_op_mutex().lock() {
        *guard = op.into();
    }
}

pub fn clear_current_op() {
    if let Ok(mut guard) = current_op_mutex().lock() {
        guard.clear();
    }
}

fn get_current_op() -> String {
    current_op_mutex()
        .lock()
        .map(|g| g.clone())
        .unwrap_or_default()
}

pub fn request_shutdown() {
    SHUTDOWN_REQUESTED.store(true, Ordering::SeqCst);
}

pub fn is_shutdown_requested() -> bool {
    SHUTDOWN_REQUESTED.load(Ordering::SeqCst)
}

pub fn check_cancelled() -> Result<()> {
    if is_shutdown_requested() && !is_in_critical_section() {
        Err(OilError::Interrupted)
    } else {
        Ok(())
    }
}

pub fn enter_critical_section() {
    CRITICAL_SECTION.store(true, Ordering::SeqCst);
}

pub fn leave_critical_section() {
    CRITICAL_SECTION.store(false, Ordering::SeqCst);
}

pub fn is_in_critical_section() -> bool {
    CRITICAL_SECTION.load(Ordering::SeqCst)
}

pub struct CriticalSection;

impl CriticalSection {
    pub fn new() -> Self {
        enter_critical_section();
        CriticalSection
    }
}

impl Drop for CriticalSection {
    fn drop(&mut self) {
        leave_critical_section();
    }
}

pub fn install_handler() {
    let _ = ctrlc::set_handler(move || {
        let op = get_current_op();
        if is_in_critical_section() {
            if op.is_empty() {
                print_interrupt("\nfinishing current operation, please wait...");
            } else {
                print_interrupt(&format!(
                    "\nfinishing {} — do not interrupt, cleaning up when done...",
                    op
                ));
            }
            request_shutdown();
        } else if is_shutdown_requested() {
            print_interrupt("\nforce quitting");
            std::process::exit(130);
        } else {
            if op.is_empty() {
                print_interrupt("\ninterrupted, cleaning up temp files...");
            } else {
                print_interrupt(&format!(
                    "\ninterrupted while {} — cleaning up temp files...",
                    op
                ));
            }
            request_shutdown();
        }
    });
}
