use gtk::{glib, prelude::*, subclass::prelude::*};

use super::CategoryType;

mod imp {
    use std::cell::Cell;

    use super::*;

    #[repr(C)]
    pub struct SidebarItemClass {
        pub parent_class: glib::object::ObjectClass,
        pub update_visibility: fn(&super::SidebarItem, for_category: CategoryType),
    }

    unsafe impl ClassStruct for SidebarItemClass {
        type Type = SidebarItem;
    }

    pub(super) fn sidebar_item_update_visibility(
        this: &super::SidebarItem,
        for_category: CategoryType,
    ) {
        let klass = this.class();
        (klass.as_ref().update_visibility)(this, for_category)
    }

    #[derive(Debug, glib::Properties)]
    #[properties(wrapper_type = super::SidebarItem)]
    pub struct SidebarItem {
        /// Whether this item is visible.
        #[property(get, set = Self::set_visible, explicit_notify, default = true)]
        pub visible: Cell<bool>,
    }

    impl Default for SidebarItem {
        fn default() -> Self {
            Self {
                visible: Cell::new(true),
            }
        }
    }

    #[glib::object_subclass]
    unsafe impl ObjectSubclass for SidebarItem {
        const NAME: &'static str = "SidebarItem";
        const ABSTRACT: bool = true;
        type Type = super::SidebarItem;
        type Class = SidebarItemClass;
    }

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

    impl SidebarItem {
        /// Set whether this item is visible.
        fn set_visible(&self, visible: bool) {
            if self.visible.get() == visible {
                return;
            }

            self.visible.set(visible);
            self.obj().notify_visible();
        }
    }
}

glib::wrapper! {
    /// Parent class of items inside the `Sidebar`.
    pub struct SidebarItem(ObjectSubclass<imp::SidebarItem>);
}

/// Public trait containing implemented methods for everything that derives from
/// `SidebarItem`.
///
/// To override the behavior of these methods, override the corresponding method
/// of `SidebarItemImpl`.
pub trait SidebarItemExt: 'static {
    /// Whether this `SidebarItem` is visible.
    ///
    /// Defaults to `true`.
    fn visible(&self) -> bool;

    /// Set the visibility of the `SidebarItem`.
    fn set_visible(&self, visible: bool);

    /// Update the visibility of the `SidebarItem` for the given `CategoryType`.
    fn update_visibility(&self, for_category: CategoryType);
}

impl<O: IsA<SidebarItem>> SidebarItemExt for O {
    fn visible(&self) -> bool {
        self.upcast_ref().visible()
    }

    fn set_visible(&self, visible: bool) {
        self.upcast_ref().set_visible(visible);
    }

    fn update_visibility(&self, for_category: CategoryType) {
        imp::sidebar_item_update_visibility(self.upcast_ref(), for_category)
    }
}

/// Public trait that must be implemented for everything that derives from
/// `SidebarItem`.
///
/// Overriding a method from this Trait overrides also its behavior in
/// `SidebarItemExt`.
pub trait SidebarItemImpl: ObjectImpl {
    fn update_visibility(&self, _for_category: CategoryType) {}
}

// Make `SidebarItem` subclassable.
unsafe impl<T> IsSubclassable<T> for SidebarItem
where
    T: SidebarItemImpl,
    T::Type: IsA<SidebarItem>,
{
    fn class_init(class: &mut glib::Class<Self>) {
        Self::parent_class_init::<T>(class.upcast_ref_mut());

        let klass = class.as_mut();

        klass.update_visibility = update_visibility_trampoline::<T>;
    }
}

// Virtual method implementation trampolines.
fn update_visibility_trampoline<T>(this: &SidebarItem, for_category: CategoryType)
where
    T: ObjectSubclass + SidebarItemImpl,
    T::Type: IsA<SidebarItem>,
{
    let this = this.downcast_ref::<T::Type>().unwrap();
    this.imp().update_visibility(for_category)
}
