use crate::prelude::*;

use crate::backend::{Manager, Message};

glib::wrapper! {
    /// Dialog showing more information about the channel.
    pub struct MessageInfoDialog(ObjectSubclass<imp::MessageInfoDialog>)
        @extends adw::Dialog, gtk::Widget,
        @implements gtk::gio::ActionGroup, gtk::gio::ActionMap, gtk::Accessible, gtk::Buildable,
            gtk::ConstraintTarget;
}

pub enum MessageInfoDialogPage {
    Receipts,
    Reactions,
    Edits,
}

impl MessageInfoDialog {
    pub fn new(message: &Message, page: MessageInfoDialogPage, manager: &Manager) -> Self {
        log::trace!("Initializing `MessageInfoDialog`");
        let s = Object::builder::<Self>()
            .property("message", message)
            .property("manager", manager)
            .build();

        message.connect_local(
            "changed",
            false,
            clone!(
                #[strong]
                s,
                move |_| {
                    s.setup();
                    None
                }
            ),
        );

        let page = match page {
            MessageInfoDialogPage::Receipts => "receipts",
            MessageInfoDialogPage::Reactions => "reactions",
            MessageInfoDialogPage::Edits => "edits",
        };

        s.imp().stack.set_visible_child_name(page);

        s.setup();

        s
    }

    fn setup(&self) {
        gspawn!(clone!(
            #[strong(rename_to = s)]
            self,
            async move {
                s.imp().setup().await;
            }
        ));
    }
}

pub mod imp {
    use crate::{
        gui::{contact_item_compact::ContactItemCompact, message_item::MessageItem},
        prelude::*,
    };

    use glib::subclass::InitializingObject;
    use gtk::{CompositeTemplate, Label};

    use crate::backend::{Manager, Message};

    #[derive(CompositeTemplate, Default, glib::Properties)]
    #[properties(wrapper_type = super::MessageInfoDialog)]
    #[template(resource = "/ui/message_info_dialog.ui")]
    pub struct MessageInfoDialog {
        #[template_child]
        page_receipts: TemplateChild<adw::ViewStackPage>,
        #[template_child]
        page_reactions: TemplateChild<adw::ViewStackPage>,
        #[template_child]
        page_edits: TemplateChild<adw::ViewStackPage>,
        #[template_child]
        list_receipts_viewed: TemplateChild<gtk::ListBox>,
        #[template_child]
        list_receipts_read: TemplateChild<gtk::ListBox>,
        #[template_child]
        list_receipts_delivered: TemplateChild<gtk::ListBox>,
        #[template_child]
        box_receipts_viewed: TemplateChild<gtk::Box>,
        #[template_child]
        box_receipts_read: TemplateChild<gtk::Box>,
        #[template_child]
        box_receipts_delivered: TemplateChild<gtk::Box>,
        #[template_child]
        status_no_receipts: TemplateChild<adw::StatusPage>,

        #[template_child]
        list_reactions: TemplateChild<gtk::ListBox>,

        #[template_child]
        list_edits: TemplateChild<gtk::ListBox>,

        #[template_child]
        pub(super) stack: TemplateChild<adw::ViewStack>,

        #[property(get, set, construct_only, type = Message)]
        message: RefCell<Option<Message>>,
        #[property(get, set, construct_only, type = Manager)]
        manager: RefCell<Option<Manager>>,
    }

    #[gtk::template_callbacks]
    impl MessageInfoDialog {
        fn reset(&self) {
            let to_clear = [
                &self.list_receipts_viewed,
                &self.list_receipts_read,
                &self.list_receipts_delivered,
                &self.list_reactions,
                &self.list_edits,
            ];

            for list in to_clear {
                list.remove_all();
            }
        }

        pub(super) async fn setup(&self) {
            self.reset();

            let message = self.obj().message().inner();

            // Receipts

            self.box_receipts_viewed
                .set_visible(message.has_viewed_receipt());
            self.box_receipts_read
                .set_visible(message.has_read_receipt());
            self.box_receipts_delivered
                .set_visible(message.has_delivery_receipt());
            self.page_receipts
                .set_visible(self.obj().message().sender().is_self());
            self.status_no_receipts.set_visible(
                !message.has_viewed_receipt()
                    && !message.has_read_receipt()
                    && !message.has_delivery_receipt(),
            );

            for r in &message.content.receipts {
                let list = match r.content.kind {
                    ftypes::ReceiptKind::Delivery => &self.list_receipts_delivered,
                    ftypes::ReceiptKind::Read => &self.list_receipts_read,
                    ftypes::ReceiptKind::Viewed => &self.list_receipts_viewed,
                };

                if let Some(contact) = self
                    .obj()
                    .manager()
                    .contact_from_id(r.sender())
                    .await
                    .ok()
                    .flatten()
                {
                    let item = ContactItemCompact::new(contact);
                    item.set_suffix(Label::new(
                        Utility::format_timestamp(r.id.timestamp.timestamp_millis() as u64)
                            .as_deref(),
                    ));
                    list.append(&item);
                } else {
                    log::warn!(
                        "Could not find contact for receipt sender: {:#?}",
                        r.sender()
                    )
                }
            }

            // Reactions

            let reactions = message.active_reactions();
            self.page_reactions.set_visible(!reactions.is_empty());

            for r in reactions {
                if let Some(contact) = self
                    .obj()
                    .manager()
                    .contact_from_id(r.sender())
                    .await
                    .ok()
                    .flatten()
                {
                    let item = ContactItemCompact::new(contact);
                    item.set_suffix(Label::new(Some(&r.content.emoji)));
                    self.list_reactions.append(&item);
                } else {
                    log::warn!(
                        "Could not find contact for reaction sender: {:#?}",
                        r.sender()
                    )
                }
            }

            // Edits

            let history = self.obj().message().edit_history().await;
            self.page_edits
                .set_visible(self.obj().message().is_edited());

            for h in history {
                let item = MessageItem::from_ftypes_for_display(&h);
                self.list_edits.append(&item);
            }
        }
    }

    #[glib::object_subclass]
    impl ObjectSubclass for MessageInfoDialog {
        const NAME: &'static str = "FlMessageInfoDialog";
        type Type = super::MessageInfoDialog;
        type ParentType = adw::Dialog;

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

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

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

    impl AdwDialogImpl for MessageInfoDialog {}
    impl WidgetImpl for MessageInfoDialog {}
}
