use super::GTagID;
use diffus::{Diffable, Same, edit::Edit};
use glib::{Object, Properties, prelude::*};
use gtk4::subclass::prelude::*;
use news_flash::models::Tag;
use std::cell::{Cell, RefCell};
use std::fmt::Display;

mod imp {
    use super::*;

    #[derive(Default, Properties)]
    #[properties(wrapper_type = super::GTag)]
    pub struct GTag {
        #[property(get, set, name = "tag-id")]
        pub id: RefCell<GTagID>,
        #[property(get, set)]
        pub label: RefCell<String>,
        #[property(get, set, nullable)]
        pub color: RefCell<Option<String>>,
        #[property(get, set)]
        pub sort_index: Cell<i32>,
        #[property(get, set)]
        pub assigned: Cell<bool>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for GTag {
        const NAME: &'static str = "NewsFlashGTag";
        type Type = super::GTag;
    }

    #[glib::derived_properties]
    impl ObjectImpl for GTag {}
}

glib::wrapper! {
    pub struct GTag(ObjectSubclass<imp::GTag>);
}

impl Default for GTag {
    fn default() -> Self {
        Object::new()
    }
}

impl Display for GTag {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "(id: {}, label: {})", self.tag_id(), self.label())
    }
}

impl From<Tag> for GTag {
    fn from(value: Tag) -> Self {
        let obj = GTag::default();
        let imp = obj.imp();
        imp.id.replace(value.tag_id.into());
        imp.label.replace(value.label);
        imp.color.replace(value.color);
        imp.sort_index.set(value.sort_index.unwrap_or(i32::MAX));
        obj
    }
}

impl Same for GTag {
    fn same(&self, other: &Self) -> bool {
        self.tag_id() == other.tag_id() && self.label() == other.label()
    }
}

impl<'a> Diffable<'a> for GTag {
    type Diff = TagDiff;

    fn diff(&'a self, other: &'a Self) -> Edit<'a, Self> {
        let self_imp = self.imp();
        let other_imp = other.imp();

        let label = if self_imp.label.borrow().as_str() == other_imp.label.borrow().as_str() {
            None
        } else {
            Some(other.label())
        };

        let color = if self_imp.color.borrow().as_deref() == other_imp.color.borrow().as_deref() {
            None
        } else {
            other.color()
        };

        if self == other && label.is_none() && color.is_none() {
            Edit::Copy(self)
        } else {
            Edit::Change(TagDiff {
                id: self.tag_id(),
                label,
                color,
            })
        }
    }
}

pub struct TagDiff {
    pub id: GTagID,
    pub label: Option<String>,
    pub color: Option<String>,
}

impl GTag {
    pub fn eq(&self, other: &Self) -> bool {
        let imp = self.imp();
        let other_imp = other.imp();
        *imp.id.borrow() == *other_imp.id.borrow()
            && *imp.color.borrow() == *other_imp.color.borrow()
            && *imp.label.borrow() == *other_imp.label.borrow()
    }
}
