use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{gio, glib, CompositeTemplate};
use ruma::events::room::message::MessageType;
use tracing::error;

use super::HistoryViewerEvent;
use crate::{gettext_f, matrix_filename, prelude::*, toast};

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

    use glib::subclass::InitializingObject;

    use super::*;

    #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
    #[template(
        resource = "/org/gnome/Fractal/ui/session/view/content/room_details/history_viewer/file_row.ui"
    )]
    #[properties(wrapper_type = super::FileRow)]
    pub struct FileRow {
        /// The file event.
        #[property(get, set = Self::set_event, explicit_notify, nullable)]
        pub event: RefCell<Option<HistoryViewerEvent>>,
        pub file: RefCell<Option<gio::File>>,
        #[template_child]
        pub button: TemplateChild<gtk::Button>,
        #[template_child]
        pub title_label: TemplateChild<gtk::Label>,
        #[template_child]
        pub size_label: TemplateChild<gtk::Label>,
    }

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

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

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

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

    impl WidgetImpl for FileRow {}
    impl BinImpl for FileRow {}

    impl FileRow {
        /// Set the file event.
        fn set_event(&self, event: Option<HistoryViewerEvent>) {
            if *self.event.borrow() == event {
                return;
            }

            if let Some(event) = &event {
                if let MessageType::File(file) = event.message_content() {
                    let filename = matrix_filename!(file, None);

                    self.title_label.set_label(&filename);
                    self.button
                        .update_property(&[gtk::accessible::Property::Label(&gettext_f(
                            // Translators: Do NOT translate the content between '{' and '}',
                            // this is a variable name.
                            "Save {filename}",
                            &[("filename", &filename)],
                        ))]);

                    if let Some(size) = file.info.and_then(|i| i.size) {
                        let size = glib::format_size(size.into());
                        self.size_label.set_label(&size);
                    } else {
                        self.size_label.set_label(&gettext("Unknown size"));
                    }
                }
            }

            self.event.replace(event);
            self.file.take();
            self.update_button();

            self.obj().notify_event();
        }

        /// Update the button for the current state.
        pub(super) fn update_button(&self) {
            if self.file.borrow().is_some() {
                self.button.set_icon_name("document-symbolic");
                self.button.set_tooltip_text(Some(&gettext("Open File")));
            } else {
                self.button.set_icon_name("save-symbolic");
                self.button.set_tooltip_text(Some(&gettext("Save File")));
            }
        }
    }
}

glib::wrapper! {
    /// A row presenting a file event.
    pub struct FileRow(ObjectSubclass<imp::FileRow>)
        @extends gtk::Widget, adw::Bin;
}

#[gtk::template_callbacks]
impl FileRow {
    /// Construct an empty `FileRow`.
    pub fn new() -> Self {
        glib::Object::new()
    }

    /// Handle when the row's button was clicked.
    #[template_callback]
    async fn button_clicked(&self) {
        let file = self.imp().file.borrow().clone();

        // If there is a file, open it.
        if let Some(file) = file {
            if let Err(error) =
                gio::AppInfo::launch_default_for_uri(&file.uri(), gio::AppLaunchContext::NONE)
            {
                error!("Could not open file: {error}");
            }
        } else {
            // Otherwise save the file.
            self.save_file().await
        }
    }

    /// Save the file of this row.
    async fn save_file(&self) {
        let Some(event) = self.event() else {
            return;
        };
        let (filename, data) = match event.get_file_content().await {
            Ok(res) => res,
            Err(error) => {
                error!("Could not get file: {error}");
                toast!(self, error.to_user_facing());

                return;
            }
        };

        let parent_window = self.root().and_downcast::<gtk::Window>().unwrap();
        let dialog = gtk::FileDialog::builder()
            .title(gettext("Save File"))
            .accept_label(gettext("Save"))
            .initial_name(filename)
            .build();

        if let Ok(file) = dialog.save_future(Some(&parent_window)).await {
            file.replace_contents(
                &data,
                None,
                false,
                gio::FileCreateFlags::REPLACE_DESTINATION,
                gio::Cancellable::NONE,
            )
            .unwrap();

            let imp = self.imp();

            imp.file.replace(Some(file));
            imp.update_button()
        }
    }
}
