use lotus_script::log;

/// Represents a variable that can be tracked for changes.
///
/// # Example
/// ```no_run
/// # use lotus_script::var_tracker::ChangeTracked;
///
/// // init:
/// let mut door_state = ChangeTracked::new(0)
///     .on_change(|&value| do_something(value))
///     .on_change(|&value| do_something_else(value));
///
/// // tick:
/// door_state.update_if_changed(1);
/// ```
///
pub struct ChangeTracked<T> {
    last_value: T,
    #[expect(clippy::type_complexity)]
    on_change: Vec<Box<dyn Fn(&T)>>,
    #[expect(clippy::type_complexity)]
    update_checker: Option<Box<dyn Fn(&T, &T) -> bool>>,
}

impl<T> ChangeTracked<T> {
    pub fn new(value: T) -> Self {
        Self {
            last_value: value,
            on_change: Default::default(),
            update_checker: None,
        }
    }

    /// Update the value without any checks and call the on_change callbacks.
    pub fn update(&mut self, value: T) {
        self.last_value = value;

        for on_change in &self.on_change {
            on_change(&self.last_value);
        }
    }

    /// Update the value if the closure returns true and call the on_change callbacks.
    pub fn update_if(&mut self, value: T, f: impl Fn(&T) -> bool) {
        if f(&value) {
            self.update(value);
        }
    }

    /// Update the value if the closure returns true and call the on_change callbacks.
    pub fn update_if_checker(&mut self, value: T) {
        if let Some(update_checker) = &self.update_checker {
            if update_checker(&self.last_value, &value) {
                self.update(value);
            }
        } else {
            log::error!("tried to use update_if_checker, but no update checker set!");
        }
    }

    /// Update the value if it is different from the last value and call the on_change callbacks.
    pub fn update_if_changed(&mut self, value: T)
    where
        T: PartialEq,
    {
        if self.last_value != value {
            self.update(value);
        }
    }

    /// Add a callback to be called when the value changes.
    pub fn on_change(mut self, on_change: impl Fn(&T) + 'static) -> Self {
        self.on_change.push(Box::new(on_change));
        self
    }

    /// Add a condition to be checked before updating the value.
    pub fn update_checker(mut self, update_condition: impl Fn(&T, &T) -> bool + 'static) -> Self {
        self.update_checker = Some(Box::new(update_condition));
        self
    }
}
