use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gsv::prelude::*;
use gtk::{
    gio,
    glib::{self, clone},
};

use super::examples::*;
use crate::{config, models::Icon};

mod imp {
    use std::cell::{OnceCell, RefCell};

    use super::*;

    #[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
    #[properties(wrapper_type = super::ExportDialog)]
    #[template(resource = "/org/gnome/design/IconLibrary/export_dialog.ui")]
    pub struct ExportDialog {
        #[property(get, set, construct_only)]
        pub icon: OnceCell<Icon>,
        #[template_child]
        pub icon_size_16: TemplateChild<gtk::Image>,
        #[template_child]
        pub icon_size_32: TemplateChild<gtk::Image>,
        #[template_child]
        pub icon_size_64: TemplateChild<gtk::Image>,
        #[template_child]
        pub languages_stack: TemplateChild<adw::ViewStack>,
        #[template_child]
        pub tags_row: TemplateChild<adw::ActionRow>,
        #[template_child]
        pub license_row: TemplateChild<adw::ActionRow>,
        #[template_child]
        pub resource_view: TemplateChild<gsv::View>,
        #[template_child]
        pub python_meson_view: TemplateChild<gsv::View>,
        #[template_child]
        pub python_view: TemplateChild<gsv::View>,
        #[template_child]
        pub rust_meson_view: TemplateChild<gsv::View>,
        #[template_child]
        pub rust_main_view: TemplateChild<gsv::View>,
        #[template_child]
        pub vala_meson_view: TemplateChild<gsv::View>,
        #[template_child]
        pub js_meson_view: TemplateChild<gsv::View>,
        #[template_child]
        pub js_view: TemplateChild<gsv::View>,
        #[template_child]
        pub c_meson_view: TemplateChild<gsv::View>,
        #[template_child]
        pub in_app_row: TemplateChild<adw::ActionRow>,
        #[template_child]
        pub as_system_row: TemplateChild<adw::ActionRow>,
        #[template_child]
        pub navigation_view: TemplateChild<adw::NavigationView>,
        #[template_child]
        pub include_label: TemplateChild<gtk::Label>,
        #[template_child]
        pub toast_overlay: TemplateChild<adw::ToastOverlay>,
        #[template_child]
        pub main_page: TemplateChild<adw::NavigationPage>,
        #[template_child]
        pub in_app_page: TemplateChild<adw::NavigationPage>,
        #[template_child]
        pub in_system_page: TemplateChild<adw::NavigationPage>,

        pub scheme: RefCell<Option<gsv::StyleScheme>>,
    }

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

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

            klass.install_action_async("export.derive", None, |widget, _, _| async move {
                if let Err(err) = widget.icon().derive().await {
                    log::error!("Failed to derive the icon {err}");
                }
            });

            // Copy Icon Name action
            klass.install_action("export.copy-icon-name", None, |widget, _, _| {
                widget.icon().copy_name();

                let toast = adw::Toast::new(&gettext("Copied"));
                widget.imp().toast_overlay.add_toast(toast);
            });

            // Copy icon to clipboard action: copy-clipboard
            klass.install_action_async("export.copy-clipboard", None, |widget, _, _| async move {
                if let Err(err) = widget.icon().copy().await {
                    log::error!("Failed to copy icon to clipboard: {err}");
                } else {
                    let toast = adw::Toast::new(&gettext("Copied"));
                    widget.imp().toast_overlay.add_toast(toast);
                }
            });

            // Save As action
            klass.install_action_async("export.save-as", None, move |widget, _, _| async move {
                let icon = widget.icon();
                let svg_filter = gtk::FileFilter::new();
                svg_filter.set_name(Some(&gettext("SVG images")));
                svg_filter.add_mime_type("image/svg+xml");

                let filters = gio::ListStore::new::<gtk::FileFilter>();
                filters.append(&svg_filter);

                let export_dialog = gtk::FileDialog::builder()
                    .title(gettext("Export a symbolic icon"))
                    .accept_label(gettext("Export"))
                    .filters(&filters)
                    .modal(true)
                    .initial_name(format!("{}.svg", icon.name()))
                    .build();

                let window = widget.root().and_downcast::<gtk::Window>().unwrap();
                match export_dialog.save_future(Some(&window)).await {
                    Ok(file) => {
                        if let Err(err) = icon.save(&file).await {
                            log::error!("Failed to export the icon {err}");
                        }
                    }
                    Err(err) => {
                        log::error!("Failed to select an icon to export {err}");
                    }
                }
            });

            // Save GResource sample
            klass.install_action_async("export.save-resource", None, |widget, _, _| async move {
                let xml_filter = gtk::FileFilter::new();
                xml_filter.set_name(Some(&gettext("XML Document")));
                xml_filter.add_mime_type("application/xml");

                let filters = gio::ListStore::new::<gtk::FileFilter>();
                filters.append(&xml_filter);

                let export_dialog = gtk::FileDialog::builder()
                    .title(gettext("Export GResource example file"))
                    .accept_label(gettext("Export"))
                    .filters(&filters)
                    .modal(true)
                    .initial_name("resources.gresource.xml")
                    .build();
                let window = widget.root().and_downcast::<gtk::Window>().unwrap();
                match export_dialog.save_future(Some(&window)).await {
                    Ok(file) => {
                        if let Err((_, err)) = file
                            .replace_contents_future(
                                GRESOURCE_EXAMPLE.as_bytes(),
                                None,
                                false,
                                gio::FileCreateFlags::NONE,
                            )
                            .await
                        {
                            log::error!("Failed to save gresource example {err}");
                        }
                    }
                    Err(err) => {
                        log::error!("Failed to select a file to save into {err}");
                    }
                }
            });
        }

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

    #[glib::derived_properties]
    impl ObjectImpl for ExportDialog {
        fn constructed(&self) {
            self.parent_constructed();
            self.obj().init();
        }
    }
    impl WidgetImpl for ExportDialog {}
    impl AdwDialogImpl for ExportDialog {}
}

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

#[gtk::template_callbacks]
impl ExportDialog {
    pub fn new(icon: Icon) -> Self {
        glib::Object::builder().property("icon", icon).build()
    }

    fn create_buffer(&self, lang: &gsv::Language, content: &str, view: &gsv::View) -> gsv::Buffer {
        let buffer = gsv::Buffer::with_language(lang);
        view.set_buffer(Some(&buffer));
        buffer.set_highlight_syntax(true);
        buffer.set_highlight_matching_brackets(false);
        TextBufferExt::set_text(&buffer, content);

        buffer
    }

    fn update_style_scheme(&self) {
        let imp = self.imp();

        let manager = adw::StyleManager::default();
        let scheme_name = if manager.is_dark() {
            "solarized-dark"
        } else {
            "solarized-light"
        };
        let scheme = gsv::StyleSchemeManager::default().scheme(scheme_name);

        for view in [
            &*imp.resource_view,
            &*imp.python_meson_view,
            &*imp.python_view,
            &*imp.rust_meson_view,
            &*imp.rust_main_view,
            &*imp.vala_meson_view,
            &*imp.js_meson_view,
            &*imp.js_view,
            &*imp.c_meson_view,
        ] {
            let buffer = view.buffer().downcast::<gsv::Buffer>().unwrap();
            buffer.set_style_scheme(scheme.as_ref());
        }
        imp.scheme.replace(scheme);
    }

    fn init(&self) {
        let imp = self.imp();

        let settings = gio::Settings::new(config::APP_ID);
        settings
            .bind("doc-page", &*imp.languages_stack, "visible-child-name")
            .build();
        let icon = self.icon();
        let icon_name = icon.name();

        imp.icon_size_16.set_icon_name(Some(&icon_name));
        imp.icon_size_32.set_icon_name(Some(&icon_name));
        imp.icon_size_64.set_icon_name(Some(&icon_name));
        imp.main_page.set_title(&icon_name.replace("-symbolic", ""));

        if icon.is_system() {
            imp.license_row.set_subtitle(&gettext("Unknown"));
            // we don't have any tags for system icons
            imp.tags_row.set_visible(false);
        } else {
            imp.tags_row.set_subtitle(&icon.tags().join(", "));
            imp.license_row
                .set_subtitle(&gettext("Creative Commons Zero v1.0 Universal"));
        }

        let language_manager = gsv::LanguageManager::default();
        let xml_lang = language_manager.language("xml").unwrap();
        let py_lang = language_manager.language("python").unwrap();
        let meson_lang = language_manager.language("meson").unwrap();
        let rust_lang = language_manager.language("rust").unwrap();
        let js_lang = language_manager.language("js").unwrap();

        let resource = gresource_example(&icon_name);
        self.create_buffer(&xml_lang, &resource, &imp.resource_view);

        // Python example
        self.create_buffer(&meson_lang, PYTHON_MESON_EXAMPLE, &imp.python_meson_view);
        self.create_buffer(&py_lang, PYTHON_EXAMPLE, &imp.python_view);

        // Rust example
        self.create_buffer(&meson_lang, RUST_MESON_EXAMPLE, &imp.rust_meson_view);
        self.create_buffer(&rust_lang, RUST_MAIN_EXAMPLE, &imp.rust_main_view);

        // Vala example
        self.create_buffer(&meson_lang, VALA_MESON_EXAMPLE, &imp.vala_meson_view);

        // JS example
        self.create_buffer(&meson_lang, JS_MESON_EXAMPLE, &imp.js_meson_view);
        self.create_buffer(&js_lang, JS_EXAMPLE, &imp.js_view);

        // C example
        self.create_buffer(&meson_lang, C_MESON_EXAMPLE, &imp.c_meson_view);

        self.update_style_scheme();

        let manager = adw::StyleManager::default();
        manager.connect_dark_notify(clone!(
            #[weak(rename_to = this)]
            self,
            move |_| this.update_style_scheme()
        ));
        imp.as_system_row.set_visible(icon.is_system());
    }

    #[template_callback]
    fn as_system_row_activated(&self, _row: adw::ActionRow) {
        let imp = self.imp();
        imp.navigation_view.push(&*imp.in_system_page);
    }

    // Switch view to: Include the icon in an App
    #[template_callback]
    fn in_app_row_activated(&self, _row: adw::ActionRow) {
        let imp = self.imp();
        imp.navigation_view.push(&*imp.in_app_page);
    }

    #[template_callback]
    fn include_label_clicked(&self, name: &str, _label: &gtk::Label) -> glib::Propagation {
        let imp = self.imp();
        match name {
            "in-app" => imp.navigation_view.push(&*imp.in_app_page),
            _ => imp.navigation_view.push(&*imp.in_system_page),
        }
        glib::Propagation::Stop
    }
}
