use std::collections::HashMap;
use std::hash::Hash;

use gtk::glib;
use gtk::prelude::*;
mod typed_list_store;
pub use typed_list_store::TypedListStore;
mod terminal_colors;
#[allow(dead_code)]
pub mod test_utils;
#[allow(unused_imports)]
pub use terminal_colors::ColorPalette;
#[allow(dead_code)]
mod widget_text;
#[allow(unused_imports)]
pub use widget_text::{StringEntry, WidgetText, WidgetTextSnapshot, extract_widget_text};
#[allow(dead_code)]
mod screenshot;
#[allow(unused_imports)]
pub use screenshot::{ScreenshotError, save_screenshot, save_screenshot_if_requested};

pub fn reconcile_properties<T: IsA<glib::Object>>(dest: &T, src: &T, properties: &[&str]) {
    for prop in dest.list_properties() {
        let name = prop.name();
        if !properties.contains(&name) {
            continue;
        }
        let other_prop = src.property::<glib::Value>(name);
        let this_prop = dest.property::<glib::Value>(name);
        // We should check if we can compare the values in other ways
        if this_prop.as_ptr() != other_prop.as_ptr() {
            dest.set_property(name, other_prop);
        }
    }
}
/// Merge `other` into `list`, keyed by `key_fn`.
///
/// Existing items whose key matches are updated in-place via property
/// reconciliation; items present only in `other` are appended. Callers must
/// supply the list of `properties` that should be synchronised.
///
/// **Thread safety:** must only be called from the GTK main thread.
///
/// **Re-entrancy:** not re-entrant. Do not invoke this from inside a signal
/// handler that could itself be triggered by the list mutations performed
/// here (e.g. `items-changed` on the same [`TypedListStore`]).
pub fn reconcile_list_by_key<T: IsA<glib::Object>, K: Hash + std::cmp::Eq>(
    list: &TypedListStore<T>,
    other: &[T],
    key_fn: impl Fn(&T) -> K,
    properties: &[&str],
) {
    let mut other_map: HashMap<K, (&T, bool)> =
        other.iter().map(|v| (key_fn(v), (v, false))).collect();
    list.retain(|item| {
        let key = key_fn(item);
        if let Some(other) = other_map.get_mut(&key) {
            reconcile_properties(item, other.0, properties);
            other.1 = true;
            true
        } else {
            false
        }
    });
    for item in other {
        let key = key_fn(item);
        if !other_map[&key].1 {
            list.append(item);
        }
    }
}
macro_rules! reaction {
    ($obj:ident . $prop:ident(), $closure:expr) => {
        let cloned = $obj.clone();
        let extractor = move || {
            cloned.$prop()
        };
        let closure = {
            #[allow(unused)]
            let $obj = $obj.clone();
            $closure
        };
        closure(extractor());

        let prop_name = stringify!($prop).replace("_", "-");
        $obj.connect_notify_local(
            Some(&prop_name),
            move |_, _| {
                closure(extractor());
            }
        );
    };
    (($($obj:ident . $prop:ident()),+), $closure:expr) => {
        let extractor = {
            $(let $obj = $obj.clone();)+
            std::rc::Rc::new(move || ($($obj.$prop()),+))
        };
        #[allow(unused)]
        let shared_closure = {
            $(let $obj = $obj.clone();)+
            std::rc::Rc::new($closure)
        };
        shared_closure.clone()(extractor.clone()());
        $(
            let c = shared_closure.clone();
            let e = extractor.clone();
            let shared_closure_with_extractor = move || {
                c(e())
            };
            let prop_name = stringify!($prop).replace("_", "-");
            $obj.connect_notify_local(
                Some(&prop_name),
                move |_,_| {
                    shared_closure_with_extractor();
                }
            );
        )+
    };
}
pub(crate) use reaction;
