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

use crate::{application::Application, models::Icon};

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

    use super::*;

    #[derive(Debug, Default, glib::Properties, gtk::CompositeTemplate)]
    #[properties(wrapper_type = super::IconWidget)]
    #[template(string = r#"
    <interface>
        <template class="IconWidget" parent="GtkFlowBoxChild">
            <property name="width-request">48</property>
            <property name="height-request">48</property>
            <property name="halign">center</property>
            <property name="valign">center</property>
            <child>
                <object class="GtkImage" id="image">
                    <property name="pixel-size">32</property>
                    <accessibility>
                        <relation name="labelled-by">IconWidget</relation>
                    </accessibility>
                </object>
            </child>
            <child>
                <object class="GtkDragSource">
                    <signal name="drag-begin" handler="on_drag_begin" swapped="true" />
                    <signal name="prepare" handler="on_drag_prepare" swapped="true" />
                </object>
            </child>
        </template>
    </interface>
    "#)]
    pub struct IconWidget {
        #[template_child]
        pub image: TemplateChild<gtk::Image>,
        #[property(get, set = Self::set_icon, explicit_notify, nullable)]
        pub icon: RefCell<Option<Icon>>,
    }

    impl IconWidget {
        fn set_icon(&self, icon: Option<&Icon>) {
            let obj = self.obj();
            if icon != self.icon.replace(icon.cloned()).as_ref() {
                let icon_name = icon.map(|icon| icon.name());
                self.image.set_icon_name(icon_name.as_deref());
                obj.set_tooltip_text(icon_name.as_deref());
                obj.notify_icon();
            }
        }
    }

    #[glib::object_subclass]
    impl ObjectSubclass for IconWidget {
        const NAME: &'static str = "IconWidget";
        type Type = super::IconWidget;
        type ParentType = gtk::FlowBoxChild;
        fn class_init(klass: &mut Self::Class) {
            klass.bind_template();
            klass.bind_template_instance_callbacks();
        }
        fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
            obj.init_template();
        }
    }

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

    impl WidgetImpl for IconWidget {}
    impl FlowBoxChildImpl for IconWidget {}
}

glib::wrapper! {
    pub struct IconWidget(ObjectSubclass<imp::IconWidget>)
        @extends gtk::Widget, gtk::FlowBoxChild,
        @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
}

#[gtk::template_callbacks]
impl IconWidget {
    #[template_callback]
    fn on_drag_begin(&self, _drag: &gdk::Drag, source: &gtk::DragSource) {
        let Some(icon) = self.icon() else {
            return;
        };
        let display = self.display();
        let icon_theme = gtk::IconTheme::for_display(&display);
        let scale = self.scale_factor();
        let paintable = icon_theme.lookup_icon(
            &icon.name(),
            &[],
            32,
            scale,
            gtk::TextDirection::None,
            gtk::IconLookupFlags::PRELOAD,
        );
        source.set_icon(Some(&paintable), 0, 0);
    }

    #[template_callback]
    fn on_drag_prepare(
        &self,
        _x: f64,
        _y: f64,
        gesture: &gtk::Gesture,
    ) -> Option<gdk::ContentProvider> {
        match self.content_provider() {
            Ok(content) => {
                gesture.set_state(gtk::EventSequenceState::Claimed);
                Some(content)
            }
            Err(err) => {
                log::debug!("Could not get content provider: {err:?}");
                gesture.set_state(gtk::EventSequenceState::Denied);
                None
            }
        }
    }

    fn content_provider(&self) -> anyhow::Result<gdk::ContentProvider> {
        let Some(icon) = self.icon() else {
            anyhow::bail!("No icon set");
        };
        let icon_name = format!("{}.svg", &icon.name());

        let file = icon.file();
        let dest = gio::File::for_path(&*Application::icons_cache().join(icon_name));
        file.copy(
            &dest,
            gio::FileCopyFlags::OVERWRITE | gio::FileCopyFlags::TARGET_DEFAULT_PERMS,
            gio::Cancellable::NONE,
            None,
        )?;

        let bytes = glib::Bytes::from_owned(file.load_contents(gio::Cancellable::NONE)?.0);

        let svg_content = gdk::ContentProvider::for_bytes("image/svg+xml", &bytes);
        let text_content = gdk::ContentProvider::for_value(&icon.name().to_value());
        let file_content = gdk::ContentProvider::for_value(&dest.to_value());

        Ok(gdk::ContentProvider::new_union(&[
            svg_content,
            text_content,
            file_content,
        ]))
    }
}

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