use crate::prelude::*;

use crate::backend::AttachmentType;

use super::components::{AttachmentAudio, AttachmentFile, AttachmentImage, AttachmentVideo};

glib::wrapper! {
    /// Displaying any attachment.
    ///
    /// This is only a base class, the implementations is attachment-type specific and one of:
    /// - [AttachmentAudio]
    /// - [AttachmentImage]
    /// - [AttachmentVideo]
    /// - [AttachmentFile]
    pub struct Attachment(ObjectSubclass<imp::Attachment>)
        @extends gtk::Widget,
        @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Box;
}

pub fn backend_to_gui(attachment: &crate::backend::Attachment) -> Attachment {
    match attachment.r#type() {
        AttachmentType::Image => AttachmentImage::new(attachment).upcast(),
        AttachmentType::Gif => AttachmentImage::new(attachment).upcast(),
        AttachmentType::Video => AttachmentVideo::new(attachment).upcast(),
        AttachmentType::File => AttachmentFile::new(attachment).upcast(),
        AttachmentType::Audio => AttachmentAudio::new(attachment).upcast(),
    }
}

pub(crate) trait AttachmentImpl: WidgetImpl + ObjectImpl + 'static {}

unsafe impl<T: AttachmentImpl> IsSubclassable<T> for Attachment {
    fn class_init(class: &mut glib::Class<Self>) {
        Self::parent_class_init::<T>(class.upcast_ref_mut());
    }
}

pub mod imp {
    use crate::prelude::*;
    use std::os::fd::AsFd;

    use ashpd::{WindowIdentifier, desktop::open_uri::OpenFileRequest};
    use gio::{File, Settings};
    use glib::subclass::{InitializingObject, Signal};
    use gtk::{CompositeTemplate, FileDialog};

    use crate::{
        backend::Manager,
        config::BASE_ID,
        gui::{error_dialog::ErrorDialog, utility::Utility},
    };

    #[derive(CompositeTemplate, Default, glib::Properties)]
    #[properties(wrapper_type = super::Attachment)]
    #[template(resource = "/ui/attachment.ui")]
    pub struct Attachment {
        #[property(get, set = Self::set_attachment, type = crate::backend::Attachment)]
        attachment: RefCell<Option<crate::backend::Attachment>>,

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

    #[gtk::template_callbacks]
    impl Attachment {
        fn set_attachment(&self, attachment: Option<crate::backend::Attachment>) {
            let settings = Settings::new(BASE_ID);
            let autoload = attachment.as_ref().is_some_and(|a| {
                settings.boolean("autodownload-images") && a.is_image()
                    || settings.boolean("autodownload-videos") && a.is_video()
                    || settings.boolean("autodownload-voice-messages") && a.is_audio()
                    || settings.boolean("autodownload-files") && a.is_file()
            });

            self.attachment.replace(attachment);
            if autoload {
                log::trace!("Autodownloading attachment");
                self.load();
            }
        }

        #[template_callback]
        pub fn load(&self) {
            let obj = self.obj();

            gspawn!(clone!(
                #[weak]
                obj,
                async move {
                    let attachment = obj.attachment();
                    attachment.load().await
                }
            ));
        }

        fn window(&self) -> crate::gui::window::Window {
            self.obj()
                .root()
                .expect("`Attachment` to have a root")
                .dynamic_cast::<crate::gui::Window>()
                .expect("Root of `Attachment` to be a `Window`.")
        }

        pub fn open(&self) {
            log::trace!("User requested to open attachment");
            if let Some(file) = self
                .attachment
                .borrow()
                .as_ref()
                .and_then(|a| a.open_file())
            {
                let obj = self.obj();

                gspawn!(clone!(
                    #[weak]
                    obj,
                    async move {
                        let identifier =
                            WindowIdentifier::from_native(&obj.native().unwrap()).await;
                        tspawn!(async move {
                            if let Err(e) = OpenFileRequest::default()
                                .ask(false)
                                .identifier(identifier)
                                .send_file(&file.as_fd())
                                .await
                            {
                                log::error!("Failed to open file: {}", e);
                            }
                        })
                        .await
                        .expect("Failed to join tokio")
                    }
                ));
            }
        }

        #[template_callback]
        fn pressed(&self) {
            let obj = self.obj();
            obj.emit_by_name::<()>("pressed", &[&obj.to_value()]);
        }

        pub fn download(&self) {
            log::trace!("User requested to download attachment");
            if let Some(attachment) = self.attachment.borrow().as_ref() {
                let mut chooser_builder = FileDialog::builder();

                if let Some(name) = attachment.name() {
                    chooser_builder = chooser_builder.initial_name(name);
                }

                if let Some(downloads) = glib::user_special_dir(glib::UserDirectory::Downloads) {
                    chooser_builder = chooser_builder.initial_folder(&File::for_path(downloads))
                }

                let chooser = chooser_builder.build();

                let obj = self.obj();
                chooser.save(
                    Some(&self.window()),
                    None::<&gio::Cancellable>,
                    clone!(
                        #[weak]
                        attachment,
                        #[weak]
                        obj,
                        move |file| {
                            if let Ok(file) = file {
                                log::trace!("User downloads attachment");
                                gspawn!(clone!(
                                    #[weak]
                                    attachment,
                                    #[weak]
                                    obj,
                                    async move {
                                        if let Err(e) = attachment.save_to_file(&file).await {
                                            let root = obj.imp().window();
                                            let dialog = ErrorDialog::new(&e.into(), &root);
                                            dialog.present(Some(&root));
                                        }
                                    }
                                ));
                            } else {
                                log::trace!("User did not save a attachment");
                            }
                        }
                    ),
                );
                log::trace!("Showing download popup");
            }
        }
    }

    #[glib::object_subclass]
    impl ObjectSubclass for Attachment {
        const NAME: &'static str = "FlAttachmentBase";
        const ABSTRACT: bool = true;
        type Type = super::Attachment;
        type ParentType = gtk::Widget;

        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 Attachment {
        fn signals() -> &'static [Signal] {
            static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| -> Vec<Signal> {
                vec![
                    Signal::builder("pressed")
                        .param_types([crate::gui::attachment::Attachment::static_type()])
                        .build(),
                ]
            });
            SIGNALS.as_ref()
        }
    }

    impl WidgetImpl for Attachment {}
    impl BoxImpl for Attachment {}
}
