pub struct UiProperty<T: Clone + PartialEq> {
    value: T,
    dirty: bool,
}

impl<T: Clone + PartialEq> UiProperty<T> {
    pub fn new(value: T) -> Self {
        Self {
            value,
            dirty: false,
        }
    }

    pub fn get(&self) -> &T {
        &self.value
    }

    pub fn set(&mut self, value: T) {
        if self.value != value {
            self.value = value;
            self.dirty = true;
        }
    }

    pub fn dirty(&self) -> bool {
        self.dirty
    }

    pub fn take_dirty(&mut self) -> bool {
        let was_dirty = self.dirty;
        self.dirty = false;
        was_dirty
    }

    pub fn clear_dirty(&mut self) {
        self.dirty = false;
    }

    pub fn map<U>(&self, transform: impl FnOnce(&T) -> U) -> U {
        transform(&self.value)
    }

    pub fn derived<U: Clone + PartialEq>(
        &self,
        transform: impl Fn(&T) -> U + 'static,
    ) -> UiDerivedProperty<T, U> {
        let initial = transform(&self.value);
        UiDerivedProperty {
            source_value: self.value.clone(),
            derived_value: initial,
            transform: Box::new(transform),
        }
    }
}

impl<T: Clone + PartialEq> std::ops::Deref for UiProperty<T> {
    type Target = T;
    fn deref(&self) -> &T {
        &self.value
    }
}

impl<T: Clone + PartialEq + Default> Default for UiProperty<T> {
    fn default() -> Self {
        Self::new(T::default())
    }
}

pub struct UiDerivedProperty<S: Clone + PartialEq, T: Clone + PartialEq> {
    source_value: S,
    derived_value: T,
    transform: Box<dyn Fn(&S) -> T>,
}

impl<S: Clone + PartialEq, T: Clone + PartialEq> UiDerivedProperty<S, T> {
    pub fn get(&self) -> &T {
        &self.derived_value
    }

    pub fn update(&mut self, source: &UiProperty<S>) -> bool {
        if *source.get() != self.source_value {
            self.source_value = source.get().clone();
            let new_value = (self.transform)(&self.source_value);
            if new_value != self.derived_value {
                self.derived_value = new_value;
                return true;
            }
        }
        false
    }
}

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

    #[test]
    fn property_new_not_dirty() {
        let prop = UiProperty::new(42);
        assert_eq!(*prop.get(), 42);
        assert!(!prop.dirty());
    }

    #[test]
    fn property_set_different_marks_dirty() {
        let mut prop = UiProperty::new(0.0_f32);
        prop.set(1.0);
        assert_eq!(*prop.get(), 1.0);
        assert!(prop.dirty());
    }

    #[test]
    fn property_set_same_stays_clean() {
        let mut prop = UiProperty::new(5);
        prop.set(5);
        assert!(!prop.dirty());
    }

    #[test]
    fn property_take_dirty_clears() {
        let mut prop = UiProperty::new(0);
        prop.set(1);
        assert!(prop.take_dirty());
        assert!(!prop.dirty());
        assert!(!prop.take_dirty());
    }

    #[test]
    fn property_clear_dirty() {
        let mut prop = UiProperty::new(0);
        prop.set(1);
        prop.clear_dirty();
        assert!(!prop.dirty());
    }

    #[test]
    fn property_deref() {
        let prop = UiProperty::new("hello".to_string());
        assert_eq!(prop.len(), 5);
    }

    #[test]
    fn property_default() {
        let prop: UiProperty<i32> = UiProperty::default();
        assert_eq!(*prop.get(), 0);
        assert!(!prop.dirty());
    }

    #[test]
    fn property_map() {
        let prop = UiProperty::new(10);
        let doubled = prop.map(|v| v * 2);
        assert_eq!(doubled, 20);
    }

    #[test]
    fn derived_initial_value() {
        let source = UiProperty::new(10);
        let derived = source.derived(|v| v * 2);
        assert_eq!(*derived.get(), 20);
    }

    #[test]
    fn derived_update_changes_value() {
        let mut source = UiProperty::new(10);
        let mut derived = source.derived(|v| v * 2);
        source.set(20);
        let changed = derived.update(&source);
        assert!(changed);
        assert_eq!(*derived.get(), 40);
    }

    #[test]
    fn derived_update_no_source_change() {
        let source = UiProperty::new(10);
        let mut derived = source.derived(|v| v * 2);
        let changed = derived.update(&source);
        assert!(!changed);
        assert_eq!(*derived.get(), 20);
    }

    #[test]
    fn derived_update_same_derived_value() {
        let mut source = UiProperty::new(10);
        let mut derived = source.derived(|v| v / 5);
        source.set(12);
        let changed = derived.update(&source);
        assert!(!changed);
        assert_eq!(*derived.get(), 2);
    }

    #[test]
    fn property_multiple_sets() {
        let mut prop = UiProperty::new(0);
        prop.set(1);
        prop.set(2);
        prop.set(3);
        assert_eq!(*prop.get(), 3);
        assert!(prop.dirty());
        prop.take_dirty();
        prop.set(3);
        assert!(!prop.dirty());
    }
}
