use std::any::Any;
use std::cell::RefCell;
use std::rc::Rc;

use crate::effect::EffectImpl;

pub(crate) struct Scope {
    pub(crate) cleanups: RefCell<Vec<Box<dyn FnOnce()>>>,
    pub(crate) effects: RefCell<Vec<Rc<EffectImpl>>>,
    pub(crate) keepers: RefCell<Vec<Box<dyn Any>>>,
}

impl Scope {
    pub(crate) fn new() -> Rc<Self> {
        Rc::new(Self {
            cleanups: RefCell::new(Vec::new()),
            effects: RefCell::new(Vec::new()),
            keepers: RefCell::new(Vec::new()),
        })
    }

    pub(crate) fn on_cleanup(&self, f: impl FnOnce() + 'static) {
        self.cleanups.borrow_mut().push(Box::new(f));
    }

    pub(crate) fn keep_alive<T: 'static>(&self, x: T) {
        self.keepers.borrow_mut().push(Box::new(x));
    }

    pub(crate) fn add_effect(&self, e: Rc<EffectImpl>) {
        self.effects.borrow_mut().push(e);
    }
}

impl Drop for Scope {
    fn drop(&mut self) {
        for cleanup in self.cleanups.borrow_mut().drain(..).rev() {
            cleanup();
        }
    }
}

thread_local! {
    static CURRENT_SCOPE: RefCell<Option<Rc<Scope>>> = const { RefCell::new(None) };
}

pub(crate) fn with_current_scope<R>(scope: &Rc<Scope>, f: impl FnOnce() -> R) -> R {
    let prev = CURRENT_SCOPE.with(|c| c.replace(Some(scope.clone())));
    let result = f();
    CURRENT_SCOPE.with(|c| *c.borrow_mut() = prev);
    result
}

pub(crate) fn current_scope() -> Rc<Scope> {
    CURRENT_SCOPE.with(|c| {
        c.borrow()
            .clone()
            .expect("current_scope() called outside an island mount")
    })
}
