use gio::SimpleAction;

use crate::{backend::Channel, prelude::*};

glib::wrapper! {
    /// The main application window.
    pub struct Window(ObjectSubclass<imp::Window>)
        @extends adw::ApplicationWindow, gtk::ApplicationWindow, adw::Window, gtk::Window, gtk::Widget,
        @implements gtk::gio::ActionGroup, gtk::gio::ActionMap, gtk::Accessible, gtk::Buildable,
            gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager;
}

impl Window {
    pub fn new(app: &adw::Application) -> Self {
        log::trace!("Initializing window");
        app.set_accels_for_action("win.settings", &["<Control>comma"]);
        app.set_accels_for_action("win.show-help-overlay", &["<Control>question"]);
        app.set_accels_for_action("win.about", &["F1"]);
        app.set_accels_for_action("window.close", &["<Control>q"]);
        app.set_accels_for_action("channel-messages.activate-input", &["<Control>i"]);
        app.set_accels_for_action("channel-messages.load-more", &["<Control>l"]);
        for i in 1..=9 {
            app.set_accels_for_action(
                &format!("channel-list.activate-channel({i})"),
                &[&format!("<Control>{i}")],
            );
        }
        app.set_accels_for_action("channel-list.toggle-search", &["<Control>f"]);

        let window = Object::builder::<Self>()
            .property("application", app)
            .build();

        let action_notification_clicked =
            SimpleAction::new("notification-clicked", Some(&u64::static_variant_type()));
        action_notification_clicked.connect_activate(clone!(
            #[weak]
            window,
            move |_, variant| {
                let variant: u64 = variant
                    .expect("Notification-clicked action to have attached data")
                    .get()
                    .expect("Variant of notification is of unexpected type");
                let manager = window.manager();
                if let Some(thread) = manager.notification_variant_to_channel(variant) {
                    window.activate_channel(thread);
                }
                window.present();
            }
        ));
        app.add_action(&action_notification_clicked);

        window
    }

    // Closes the window, even if hide-on-close is set.
    pub fn kill(&self) {
        self.set_hide_on_close(false);
        self.close();
    }

    pub fn destroy_if_invisible(&self) {
        if !self.get_visible() {
            self.destroy();
        }
    }

    fn save_window_size(&self) -> Result<(), glib::BoolError> {
        let imp = self.imp();

        let (width, height) = self.default_size();

        imp.settings.set_int("window-width", width)?;
        imp.settings.set_int("window-height", height)?;

        imp.settings
            .set_boolean("is-maximized", self.is_maximized())?;

        Ok(())
    }

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

        let width = imp.settings.int("window-width");
        let height = imp.settings.int("window-height");
        let is_maximized = imp.settings.boolean("is-maximized");

        self.set_default_size(width, height);

        if is_maximized {
            self.maximize();
        }
    }

    fn activate_channel(&self, channel: Channel) {
        self.imp().channel_list.set_active_channel(Some(channel));
    }

    pub(crate) fn settings(&self) -> gio::Settings {
        self.imp().settings.clone()
    }
}

pub mod imp {
    use crate::{config, prelude::*};
    use std::{env, path::PathBuf};

    use adw::{AlertDialog, EntryRow, ResponseAppearance};
    use gio::{Cancellable, Settings, SimpleAction, SimpleActionGroup};
    use glib::subclass::InitializingObject;
    use glib::{BindingFlags, BoxedAnyObject, Propagation};
    use gtk::{Builder, CompositeTemplate};

    use crate::backend::{Channel, SetupResult};
    use crate::gui::channel_info_dialog::ChannelInfoDialog;
    use crate::gui::linked_devices_window::LinkedDevicesWindow;
    use crate::gui::new_channel_dialog::NewChannelDialog;
    use crate::{
        backend::Manager,
        config::BASE_ID,
        gui::{
            channel_list::ChannelList, channel_messages::ChannelMessages,
            error_dialog::ErrorDialog, preferences_window::PreferencesWindow,
            setup_window::SetupWindow,
        },
    };

    #[derive(CompositeTemplate, glib::Properties)]
    #[template(resource = "/ui/window.ui")]
    #[properties(wrapper_type = super::Window)]
    pub struct Window {
        #[template_child]
        stack: TemplateChild<gtk::Stack>,
        #[template_child]
        status_page_startup: TemplateChild<adw::StatusPage>,

        #[template_child]
        split_view: TemplateChild<adw::NavigationSplitView>,
        #[template_child]
        pub(super) channel_list: TemplateChild<ChannelList>,
        #[template_child]
        subtitle_label: TemplateChild<gtk::Label>,
        #[template_child]
        channel_messages: TemplateChild<ChannelMessages>,
        #[template_child]
        new_channel_dialog: TemplateChild<NewChannelDialog>,

        pub(super) settings: gio::Settings,

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

    impl Default for Window {
        fn default() -> Self {
            Self {
                stack: Default::default(),
                status_page_startup: Default::default(),
                split_view: Default::default(),
                channel_list: Default::default(),
                subtitle_label: Default::default(),
                channel_messages: Default::default(),
                new_channel_dialog: Default::default(),
                manager: Default::default(),
                settings: Settings::new(BASE_ID),
            }
        }
    }

    #[gtk::template_callbacks]
    impl Window {
        fn setup_actions(&self) {
            log::trace!("Setting up window actions");

            // Global window actions.

            log::trace!("Setting up preferences-window action");
            let obj = self.obj();
            let action_settings = SimpleAction::new("settings", None);
            action_settings.connect_activate(clone!(
                #[weak]
                obj,
                move |_, _| {
                    let settings = PreferencesWindow::new();
                    settings.present(Some(&obj));
                }
            ));

            let action_clear_messages = SimpleAction::new("clear-messages", None);
            action_clear_messages.connect_activate(clone!(
                #[weak]
                obj,
                move |_, _| {
                    log::trace!("User requested to clear messages");
                    let builder = Builder::from_resource("/ui/dialog_clear_messages.ui");
                    let dialog: AlertDialog = builder
                        .object("dialog")
                        .expect("dialog_clear_messages.ui to have at least one object dialog");
                    dialog.connect_response(
                        None,
                        clone!(
                            #[weak]
                            obj,
                            move |_dialog, response| {
                                if response == "clear" {
                                    log::info!("Clear messages device");
                                    if let Some(man) = obj.imp().manager.borrow().as_ref() {
                                        gspawn!(clone!(
                                            #[strong]
                                            man,
                                            async move {
                                                if let Err(e) = man.clear_messages().await {
                                                    log::error!("Failed to clear db: {}", e);
                                                }
                                            }
                                        ));
                                    }
                                    log::trace!("Closing the window after clear");
                                    obj.kill();
                                }
                            }
                        ),
                    );
                    dialog.present(Some(&obj));
                }
            ));

            log::trace!("Setting up unlink action");
            let action_unlink = SimpleAction::new("unlink", None);
            action_unlink.connect_activate(clone!(
                #[weak]
                obj,
                move |_, _| {
                    log::trace!("User requested to unlink the device");
                    let builder = Builder::from_resource("/ui/dialog_unlink.ui");
                    let dialog: AlertDialog = builder
                        .object("dialog")
                        .expect("dialog_unlink.ui to have at least one object dialog");
                    dialog.connect_response(
                        None,
                        clone!(
                            #[weak]
                            obj,
                            move |_dialog, response| {
                                if response == "unlink-keep" {
                                    log::info!("Unlinking device");
                                    if let Some(man) = obj.imp().manager.borrow().as_ref() {
                                        gspawn!(clone!(
                                            #[strong]
                                            obj,
                                            #[strong]
                                            man,
                                            async move {
                                                if let Err(e) = man.clear_registration().await {
                                                    log::error!("Failed to clear db: {}", e);
                                                }

                                                log::trace!("Closing the window after unlink");
                                                obj.kill();
                                            }
                                        ));
                                    }
                                } else if response == "unlink-delete" {
                                    log::info!("Unlinking device");
                                    if let Some(man) = obj.imp().manager.borrow().as_ref() {
                                        gspawn!(clone!(
                                            #[strong]
                                            obj,
                                            #[strong]
                                            man,
                                            async move {
                                                if let Err(e) = man.clear_registration().await {
                                                    log::error!("Failed to clear db: {}", e);
                                                }
                                                if let Err(e) = man.clear_contents().await {
                                                    log::error!("Failed to clear db: {}", e);
                                                }

                                                log::trace!("Closing the window after unlink");
                                                obj.kill();
                                            }
                                        ));
                                    }
                                }
                            }
                        ),
                    );
                    dialog.present(Some(&obj));
                }
            ));

            log::trace!("Setting up submit-captcha action");
            let action_submit_captcha = SimpleAction::new("submit-captcha", None);
            action_submit_captcha.connect_activate(clone!(
                #[weak]
                obj,
                move |_, _| {
                    log::trace!("User requested to submit a captcha the device");
                    let builder = Builder::from_resource("/ui/submit_captcha_dialog.ui");
                    let dialog: AlertDialog = builder
                        .object("dialog")
                        .expect("submit_captcha_dialog.ui to have at least one object dialog");
                    let entry_token: EntryRow = builder
                        .object("entry_token")
                        .expect("submit_captcha_dialog.ui to have at least one object entry_token");
                    let entry_captcha: EntryRow = builder.object("entry_captcha").expect(
                        "submit_captcha_dialog.ui to have at least one object entry_captcha",
                    );
                    dialog.connect_response(
                        None,
                        clone!(
                            #[weak]
                            obj,
                            #[weak]
                            entry_token,
                            #[weak]
                            entry_captcha,
                            move |_dialog, response| {
                                if response == "submit" {
                                    log::info!("Unlinking device");
                                    if let Some(man) = obj.imp().manager.borrow().as_ref() {
                                        let token = entry_token.text();
                                        let captcha = entry_captcha.text();
                                        gspawn!(clone!(
                                            #[weak]
                                            man,
                                            #[weak]
                                            obj,
                                            async move {
                                                if let Err(e) = man
                                                    .submit_recaptcha_challenge(&token, &captcha)
                                                    .await
                                                {
                                                    log::error!(
                                                        "Failed to submit recaptcha: {}",
                                                        e
                                                    );
                                                    let dialog = ErrorDialog::new(&e, &obj);
                                                    dialog.present(Some(&obj));
                                                }
                                            }
                                        ));
                                    }
                                }
                            }
                        ),
                    );
                    dialog.present(Some(&obj));
                }
            ));

            log::trace!("Setting up sync-contacts action");
            let action_sync_contacts = SimpleAction::new("sync-contacts", None);
            action_sync_contacts.connect_activate(clone!(
                #[weak]
                obj,
                move |_, _| {
                    log::trace!("User requested to synchronize contacts");
                    if let Some(man) = obj.imp().manager.borrow().as_ref() {
                        gspawn!(clone!(
                            #[weak]
                            man,
                            #[weak]
                            obj,
                            async move {
                                if let Err(e) = man.request_contacts_sync().await {
                                    log::error!("Failed to synchronize contacts: {}", e);
                                    let dialog = ErrorDialog::new(&e, &obj);
                                    dialog.present(Some(&obj));
                                }
                            }
                        ));
                    };
                }
            ));

            let action_show_help_overlay = SimpleAction::new("show-help-overlay", None);
            action_show_help_overlay.connect_activate(clone!(
                #[weak]
                obj,
                move |_, _| {
                    let builder = Builder::from_resource("/ui/shortcuts.ui");
                    let shortcuts_window: adw::ShortcutsDialog = builder
                        .object("help_overlay")
                        .expect("shortcuts.ui to have at least one object help_overlay");
                    shortcuts_window.present(Some(&obj));
                }
            ));

            log::trace!("Setting up about-page action");
            let action_about = SimpleAction::new("about", None);
            action_about.connect_activate(clone!(
                #[weak(rename_to = window)]
                obj,
                move |_, _| {
                    let about_dialog = adw::AboutDialog::from_appdata(
                        &(config::RESOURCES_PATH.to_owned() + config::APP_ID + ".metainfo.xml"),
                        Some(env!("CARGO_PKG_VERSION")),
                    );

                    about_dialog.set_comments(env!("CARGO_PKG_DESCRIPTION"));
                    about_dialog.set_developers(
                        &(env!("CARGO_PKG_AUTHORS").split(':').collect::<Vec<&str>>()),
                    );
                    // translators: One per line: How you want to be credited as a, e.g. by the name you use, and optionally an email address ("Edgar Allan Poe <edgar@poe.com>")
                    about_dialog.set_translator_credits(&gettextrs::gettext("translator-credits"));
                    about_dialog.set_artists(&["David Lapshin <ddaudix@gmail.com>"]);
                    about_dialog.add_link("GitLab", "https://gitlab.com/schmiddi-on-mobile/flare");

                    about_dialog.present(Some(&window));
                }
            ));

            log::trace!("Setting up kill action");
            let action_kill = SimpleAction::new("kill", None);
            action_kill.connect_activate(clone!(
                #[weak]
                obj,
                move |_, _| {
                    obj.kill();
                }
            ));

            log::trace!("Setting up channel information action");
            let action_channel_information = SimpleAction::new("channel-information", None);
            action_channel_information.connect_activate(clone!(
                #[weak]
                obj,
                move |_, _| {
                    log::trace!("Requested channel info");
                    let Some(channel) = obj.imp().channel_messages.active_channel() else {
                        return;
                    };
                    let channel_info = ChannelInfoDialog::new(&channel, &obj.manager());
                    channel_info.present(Some(&obj));
                }
            ));

            log::trace!("Setting up channel clear messages action");
            let action_channel_clear_messages = SimpleAction::new("channel-clear-messages", None);
            action_channel_clear_messages.connect_activate(clone!(
                #[weak]
                obj,
                move |_, _| {
                    log::trace!("Requested clearing messages of channels");
                    let confirmation_dialog = AlertDialog::builder()
                        .heading(gettextrs::gettext("Remove Messages"))
                        .body(gettextrs::gettext(
                            "This will remove all locally stored messages from this channel",
                        ))
                        .close_response("cancel")
                        .default_response("cancel")
                        .build();
                    confirmation_dialog.add_response("cancel", &gettextrs::gettext("Cancel"));
                    confirmation_dialog
                        .add_response("remove", &gettextrs::gettext("Remove Messages"));
                    confirmation_dialog
                        .set_response_appearance("remove", ResponseAppearance::Destructive);
                    confirmation_dialog.choose(
                        Some(&obj),
                        None::<&Cancellable>,
                        clone!(
                            #[weak]
                            obj,
                            move |response| {
                                if response == "remove" {
                                    gspawn!(clone!(
                                        #[strong]
                                        obj,
                                        async move {
                                            if let Err(e) =
                                                obj.imp().channel_messages.clear_messages().await
                                            {
                                                let dialog = ErrorDialog::new(&e, &obj);
                                                dialog.present(Some(&obj));
                                            }
                                        }
                                    ));
                                }
                            }
                        ),
                    );
                }
            ));

            self.channel_messages
                .bind_property("active-channel", &action_channel_information, "enabled")
                .transform_to(|_, c: Option<Channel>| Some(c.is_some()))
                .flags(BindingFlags::SYNC_CREATE)
                .build();

            self.channel_messages
                .bind_property("active-channel", &action_channel_clear_messages, "enabled")
                .transform_to(|_, c: Option<Channel>| Some(c.is_some()))
                .flags(BindingFlags::SYNC_CREATE)
                .build();

            log::trace!("Adding a action to the group");
            let actions = SimpleActionGroup::new();
            obj.insert_action_group("win", Some(&actions));
            actions.add_action(&action_settings);
            actions.add_action(&action_clear_messages);
            actions.add_action(&action_unlink);
            actions.add_action(&action_submit_captcha);
            actions.add_action(&action_sync_contacts);
            actions.add_action(&action_show_help_overlay);
            actions.add_action(&action_about);
            actions.add_action(&action_kill);
            actions.add_action(&action_channel_information);
            actions.add_action(&action_channel_clear_messages);

            // Channel messages actions.

            let action_activate_input = SimpleAction::new("activate-input", None);
            action_activate_input.connect_activate(clone!(
                #[strong(rename_to = channel_messages)]
                self.channel_messages,
                move |_, _| {
                    channel_messages.focus_input();
                }
            ));
            let action_load_more = SimpleAction::new("load-more", None);
            action_load_more.connect_activate(clone!(
                #[strong(rename_to = channel_messages)]
                self.channel_messages,
                move |_, _| {
                    channel_messages.load_more();
                }
            ));
            let actions = SimpleActionGroup::new();
            obj.insert_action_group("channel-messages", Some(&actions));
            actions.add_action(&action_activate_input);
            actions.add_action(&action_load_more);

            // Channel list actions.

            let action_activate_channel =
                SimpleAction::new("activate-channel", Some(&i32::static_variant_type()));
            action_activate_channel.connect_activate(clone!(
                #[strong(rename_to = channel_list)]
                self.channel_list,
                move |_, parameter| {
                    let parameter = parameter
                        .expect("Could not get parameter.")
                        .get::<i32>()
                        .expect("The variant needs to be of type `i32`.");
                    channel_list.activate_row((parameter - 1).try_into().unwrap_or_default());
                }
            ));
            let action_toggle_search = SimpleAction::new("toggle-search", None);
            action_toggle_search.connect_activate(clone!(
                #[strong(rename_to = channel_list)]
                self.channel_list,
                move |_, _| {
                    channel_list.toggle_search();
                }
            ));
            let actions = SimpleActionGroup::new();
            obj.insert_action_group("channel-list", Some(&actions));
            actions.add_action(&action_activate_channel);
            actions.add_action(&action_toggle_search);
        }

        fn setup_active_window_handler(&self) {
            let obj = self.obj();
            obj.connect_is_active_notify(clone!(
                #[weak(rename_to = w)]
                self,
                move |window| {
                    w.channel_list
                        .set_active(window.is_active() && window.is_visible())
                }
            ));
            obj.connect_visible_notify(clone!(
                #[weak(rename_to = w)]
                self,
                move |window| {
                    w.channel_list
                        .set_active(window.is_active() && window.is_visible())
                }
            ));
        }

        // Requires the manager to be set up. Therefore, postponed.
        fn setup_linked_devices_action(&self) {
            let obj = self.obj();

            log::trace!("Setting up linked-devices action");
            let action_linked_devices = SimpleAction::new("linked-devices", None);
            action_linked_devices.connect_activate(clone!(
                #[weak]
                obj,
                move |_, _| {
                    log::trace!("User requested to view linked devices");
                    let win = LinkedDevicesWindow::new(obj.manager(), &obj);
                    win.present(Some(&obj));
                }
            ));

            self.obj()
                .manager()
                .bind_property("is-primary", &action_linked_devices, "enabled")
                .flags(BindingFlags::SYNC_CREATE)
                .build();

            let actions = SimpleActionGroup::new();
            obj.insert_action_group("win-managed", Some(&actions));
            actions.add_action(&action_linked_devices);
        }

        #[template_callback]
        fn handle_search_clicked(&self) {
            self.channel_list.toggle_search();
        }

        #[template_callback]
        fn handle_show_content(&self) {
            log::trace!("Active view changed in the SplitView");
            if !self.split_view.property::<bool>("show-content") {
                self.channel_list.set_active(false);
            }
        }

        #[template_callback]
        fn handle_go_forward(&self) {
            log::trace!("Go forward in the SplitView");
            self.split_view.set_show_content(true);
            self.channel_list.set_active(self.obj().is_active());
        }

        #[template_callback]
        fn handle_add_conversation_clicked(&self) {
            let dialog = &self.new_channel_dialog;
            dialog.present_for_selection(&self.obj().manager().available_channels());
        }

        #[template_callback]
        fn handle_new_channel(&self, channel: Channel) {
            self.channel_list.add_channel(channel);
        }
    }

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

        fn class_init(klass: &mut Self::Class) {
            crate::gui::channel_list::ChannelList::ensure_type();
            crate::gui::channel_messages::ChannelMessages::ensure_type();
            crate::gui::setup_window::SetupWindow::ensure_type();
            crate::gui::error_dialog::ErrorDialog::ensure_type();
            crate::gui::new_channel_dialog::NewChannelDialog::ensure_type();

            Self::bind_template(klass);
            Self::bind_template_callbacks(klass);
            crate::gui::utility::Utility::bind_template_callbacks(klass);
        }

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

    #[glib::derived_properties]
    impl ObjectImpl for Window {
        fn constructed(&self) {
            log::trace!("Constructed window");
            let obj = self.obj();
            self.parent_constructed();
            obj.imp().setup_actions();

            // Devel Profile
            if crate::config::PROFILE == "Devel" {
                obj.add_css_class("devel");
            }

            obj.load_window_size();

            gspawn!(clone!(
                #[strong]
                obj,
                async move {
                    log::trace!("Constructing path for configuration");
                    let path = PathBuf::from(env::var("FLARE_DATA_PATH").unwrap_or_else(|_| {
                        env::var("XDG_DATA_HOME")
                            .map(|s| s + "/flare/")
                            .unwrap_or_else(|_| {
                                env::var("HOME")
                                    .map(|s| s + "/.local/share/flare/")
                                    .expect("Could not find $HOME")
                            })
                    }));
                    log::trace!("Setup manager for Window");
                    let manager = Manager::new(obj.property::<gio::Application>("application"));
                    obj.set_property("manager", Some(&manager));
                    obj.imp().setup_linked_devices_action();
                    obj.imp().setup_active_window_handler();

                    let setup_window: RefCell<Option<SetupWindow>> = RefCell::default();

                    manager.connect_local(
                        "setup-result",
                        false,
                        clone!(
                            #[weak]
                            obj,
                            #[weak]
                            manager,
                            #[strong]
                            setup_window,
                            #[upgrade_or_default]
                            move |r| {
                                // r[0] is the manager
                                let result = r[1]
                                    .get::<BoxedAnyObject>()
                                    .expect("Setup-Result to be BoxedAnyObject");
                                let result: &mut SetupResult = &mut result.borrow_mut();
                                let mut setup_window = setup_window.borrow_mut();

                                if !matches!(result, SetupResult::Finished)
                                    || setup_window.is_some()
                                {
                                    let setup = setup_window
                                        .get_or_insert_with(|| SetupWindow::new(manager, &obj));
                                    setup.handle_setup_result(result);

                                    if matches!(result, SetupResult::Finished) {
                                        *setup_window = None;
                                    }
                                }

                                None
                            }
                        ),
                    );

                    manager.connect_local(
                        "error",
                        false,
                        clone!(
                            #[weak]
                            obj,
                            #[upgrade_or_default]
                            move |r| {
                                // r[0] is the manager
                                let result = r[1]
                                    .get::<BoxedAnyObject>()
                                    .expect("Error to be BoxedAnyObject");
                                let error: &ApplicationError = &mut result.borrow();

                                let dialog = ErrorDialog::new(error, &obj);
                                dialog.present(Some(&obj));

                                None
                            }
                        ),
                    );

                    manager.connect_notify_local(
                        Some("finished-setup"),
                        clone!(
                            #[weak]
                            obj,
                            #[upgrade_or_default]
                            move |_, _| {
                                obj.imp().channel_list.scroll_up();
                                obj.imp().stack.set_visible_child_name("main");
                            }
                        ),
                    );

                    manager
                        .bind_property(
                            "last-message-datetime",
                            &obj.imp().status_page_startup.get(),
                            "description",
                        )
                        .transform_to(|_, datetime: Option<glib::DateTime>| {
                            if let Some(datetime) = datetime {
                                Some(gettextrs::gettext("Loading messages from: {date}").replace(
                                    "{date}",
                                    &Utility::format_date_human(&datetime).unwrap_or_default(),
                                ))
                            } else {
                                Some(gettextrs::gettext("Preparing to receive messages"))
                            }
                        })
                        .flags(BindingFlags::SYNC_CREATE)
                        .build();

                    if let Err(e) = manager.init(&path).await {
                        let dialog = ErrorDialog::new(&e, &obj);
                        dialog.present(Some(&obj));
                    }
                }
            ));
        }
    }

    impl WidgetImpl for Window {}
    impl WindowImpl for Window {
        fn close_request(&self) -> Propagation {
            if let Err(err) = self.obj().save_window_size() {
                log::warn!("Failed to save window state, {}", err);
            }

            if !self.obj().hides_on_close() {
                gspawn!(clone!(
                    #[strong(rename_to = manager)]
                    self.obj().manager(),
                    async move {
                        manager.shutdown().await;
                    }
                ));
            }

            self.parent_close_request()
        }
    }
    impl ApplicationWindowImpl for Window {}
    impl AdwWindowImpl for Window {}
    impl AdwApplicationWindowImpl for Window {}
}
