use crate::app::App;
use crate::gobject_models::{GTag, GTagID};
use crate::i18n::i18n;
use crate::util::{GtkUtil, constants};
use gdk4::Rectangle;
use gio::{Menu, MenuItem};
use glib::{Properties, SignalHandlerId, clone, subclass::*};
use gtk4::{
    Accessible, Actionable, Buildable, CompositeTemplate, ConstraintTarget, DrawingArea, ListBoxRow, PopoverMenu,
    Widget, prelude::*, subclass::prelude::*,
};
use news_flash::models::PluginCapabilities;
use once_cell::sync::Lazy;
use std::cell::RefCell;
use std::str;

mod imp {
    use super::*;

    #[derive(Debug, Default, CompositeTemplate, Properties)]
    #[properties(wrapper_type = super::TagRow)]
    #[template(file = "data/resources/ui_templates/sidebar/tag_row.blp")]
    pub struct TagRow {
        #[template_child]
        pub tag_color: TemplateChild<DrawingArea>,
        #[template_child]
        pub context_popover: TemplateChild<PopoverMenu>,

        #[property(get, set)]
        pub model: RefCell<GTag>,

        pub color_signal: RefCell<Option<SignalHandlerId>>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for TagRow {
        const NAME: &'static str = "TagRow";
        type ParentType = ListBoxRow;
        type Type = super::TagRow;

        fn class_init(klass: &mut Self::Class) {
            klass.bind_template();
            klass.bind_template_callbacks();
        }

        fn instance_init(obj: &InitializingObject<Self>) {
            obj.init_template();
        }
    }

    #[glib::derived_properties]
    impl ObjectImpl for TagRow {
        fn signals() -> &'static [Signal] {
            static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
                vec![
                    Signal::builder("activated")
                        .param_types([super::TagRow::static_type()])
                        .build(),
                ]
            });
            SIGNALS.as_ref()
        }

        fn constructed(&self) {
            self.tag_color.set_draw_func(clone!(
                #[weak(rename_to = this)]
                self.obj(),
                move |_drawing_area, ctx, _width, _height| {
                    GtkUtil::draw_color_cirlce(
                        ctx,
                        this.model().color().as_deref().unwrap_or(constants::TAG_DEFAULT_COLOR),
                        None,
                    );
                }
            ));

            self.parent_constructed();
        }
    }

    impl WidgetImpl for TagRow {}

    impl ListBoxRowImpl for TagRow {}

    #[gtk4::template_callbacks]
    impl TagRow {
        #[template_callback]
        pub fn on_left_click(&self, press_count: i32, _x: f64, _y: f64) {
            if press_count != 1 {
                return;
            }

            self.obj().activate();
        }

        #[template_callback]
        pub fn on_right_click(&self, press_count: i32, x: f64, y: f64) {
            if press_count != 1 {
                return;
            }

            if App::default().is_offline() {
                return;
            }

            if !App::default()
                .features()
                .as_ref()
                .contains(PluginCapabilities::SUPPORT_TAGS)
            {
                return;
            }

            let rect = Rectangle::new(x as i32, y as i32, 0, 0);
            self.context_popover.set_pointing_to(Some(&rect));
            self.context_popover.popup();
        }

        #[template_callback]
        pub fn on_long_press(&self, x: f64, y: f64) {
            if App::default().is_offline() {
                return;
            }

            if !App::default()
                .features()
                .as_ref()
                .contains(PluginCapabilities::SUPPORT_TAGS)
            {
                return;
            }

            let rect = Rectangle::new(x as i32, y as i32, 0, 0);
            self.context_popover.set_pointing_to(Some(&rect));
            self.context_popover.popup();
        }

        pub(super) fn context_menu(tag_id: &GTagID, label: String) -> Menu {
            let tag_id_variant = tag_id.to_variant();

            let model = Menu::new();
            let mark_read_item = MenuItem::new(Some(&i18n("Set Read")), None);
            let edit_item = MenuItem::new(Some(&i18n("Edit")), None);
            let delete_item = MenuItem::new(Some(&i18n("Delete")), None);

            mark_read_item.set_action_and_target_value(Some("win.mark-tag-read"), Some(&tag_id_variant));
            edit_item.set_action_and_target_value(Some("win.edit-tag-dialog"), Some(&tag_id_variant));
            delete_item
                .set_action_and_target_value(Some("win.enqueue-delete-tag"), Some(&(tag_id, label).to_variant()));

            model.append_item(&mark_read_item);
            model.append_item(&edit_item);
            model.append_item(&delete_item);
            model
        }
    }
}

glib::wrapper! {
    pub struct TagRow(ObjectSubclass<imp::TagRow>)
        @extends Widget, ListBoxRow,
        @implements Accessible, Actionable, Buildable, ConstraintTarget;
}

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

impl TagRow {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn bind_model(&self, model: &GTag) {
        let imp = self.imp();
        let tag_id = model.tag_id();

        if tag_id == self.model().tag_id() {
            return;
        }

        let menu_model = imp::TagRow::context_menu(&tag_id, model.label());
        imp.context_popover.set_menu_model(Some(&menu_model));

        if let Some(signal_id) = imp.color_signal.take() {
            self.model().disconnect(signal_id);
        }

        let signal = model.connect_color_notify(clone!(
            #[weak]
            imp,
            #[upgrade_or_panic]
            move |_tag| {
                imp.tag_color.queue_draw();
            }
        ));
        imp.color_signal.replace(Some(signal));

        self.set_model(model);
    }

    pub fn activate(&self) {
        self.emit_by_name::<()>("activated", &[&self.clone()]);
    }

    pub fn connect_activated<F: Fn(&Self) + 'static>(&self, f: F) -> SignalHandlerId {
        self.connect_local("activated", false, move |args| {
            let row = args[1].get::<Self>().expect("The value needs to be of type `TagRow`");
            f(&row);
            None
        })
    }
}
