use adw::subclass::prelude::*;
use gettextrs::gettext;
use gtk::{gdk, gio, glib, prelude::*};

use crate::{
    config::{APP_ID, PKGDATADIR, PROFILE, VERSION},
    recents::RecentManager,
    widgets::Window,
};

mod imp {
    use std::{cell::OnceCell, path::PathBuf};

    use super::*;

    #[derive(Debug, Default)]
    pub struct Application {
        pub(super) windows: gtk::WindowGroup,
        pub(super) history: RecentManager,
        pub icon_theme: OnceCell<gtk::IconTheme>,
    }

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

    impl ObjectImpl for Application {
        fn constructed(&self) {
            self.parent_constructed();
            tracing::debug!("Application::constructed");

            self.obj().add_main_option(
                "new-window",
                glib::Char::from(b'w'),
                glib::OptionFlags::NONE,
                glib::OptionArg::None,
                &gettext("Open a new window"),
                None,
            );
        }
    }

    impl ApplicationImpl for Application {
        fn handle_local_options(&self, options: &glib::VariantDict) -> glib::ExitCode {
            let app = self.obj();

            if options.contains("new-window") {
                if let Err(err) = app.register(None::<&gio::Cancellable>) {
                    tracing::error!("Failed to register the application: {err}");
                }

                if app.is_remote() {
                    app.activate_action("new-window", None);
                    return glib::ExitCode::SUCCESS;
                }
            }

            tracing::debug!("Application::handle_local_options");
            self.parent_handle_local_options(options)
        }

        fn activate(&self) {
            self.parent_activate();
            let app = self.obj();
            tracing::debug!("Application::activate");

            if let Some(window) = app.active_window() {
                window.present();
                return;
            }

            let window = app.create_window();
            window.present();
        }

        fn startup(&self) {
            self.parent_startup();
            let app = self.obj();
            tracing::debug!("Application::startup");
            // Set icons for shell
            gtk::Window::set_default_icon_name(APP_ID);

            if let Some(display) = gdk::Display::default() {
                let icon_theme = gtk::IconTheme::for_display(&display);

                for uri in self.history.history().iter() {
                    let icons_dir = uri
                        .file_name()
                        .unwrap()
                        .to_str()
                        .unwrap()
                        .trim_end_matches(".svg")
                        .replace('.', "_");
                    let icons_dir = [glib::user_cache_dir(), icons_dir.into(), "icons/".into()]
                        .iter()
                        .collect::<PathBuf>();
                    icon_theme.add_search_path(icons_dir);
                }
                self.icon_theme.set(icon_theme).unwrap();
            }

            app.setup_actions();
        }

        fn open(&self, files: &[gio::File], _hint: &str) {
            for file in files.iter().cloned() {
                let window = self.obj().create_window();
                let ctx = glib::MainContext::default();
                ctx.spawn_local(async move {
                    window.parse_project(file).await;
                    window.present();
                });
            }
        }
    }

    impl GtkApplicationImpl for Application {
        fn window_removed(&self, window: &gtk::Window) {
            self.windows.remove_window(window);
            self.parent_window_removed(window);
        }
    }

    impl AdwApplicationImpl for Application {}
}

glib::wrapper! {
    pub struct Application(ObjectSubclass<imp::Application>)
        @extends gio::Application, gtk::Application, adw::Application,
        @implements gio::ActionMap, gio::ActionGroup;
}

impl Application {
    fn create_window(&self) -> Window {
        let imp = self.imp();
        let window = Window::new(self);
        window.set_history(imp.history.clone());
        imp.windows.add_window(&window);
        window
    }

    fn window(&self) -> Window {
        self.active_window().and_downcast().unwrap()
    }

    fn setup_actions(&self) {
        // Quit
        let action_quit = gio::ActionEntry::builder("quit")
            .activate(move |app: &Self, _, _| {
                // This is needed to trigger the delete event and saving the window state
                app.window().close();
                app.quit();
            })
            .build();

        // About
        let action_about = gio::ActionEntry::builder("about")
            .activate(move |app: &Self, _, _| {
                app.show_about_dialog();
            })
            .build();

        // New window
        let action_new_window = gio::ActionEntry::builder("new-window")
            .activate(move |app: &Self, _, _| {
                let window = app.create_window();
                window.present();
            })
            .build();
        self.add_action_entries([action_quit, action_about, action_new_window]);

        // Sets up keyboard shortcuts

        self.set_accels_for_action("app.quit", &["<primary>q"]);
        self.set_accels_for_action("app.about", &["<primary>comma"]);
        self.set_accels_for_action("app.new-window", &["<primary>n"]);
        self.set_accels_for_action("win.open", &["<primary>o"]);
        self.set_accels_for_action("win.reload", &["<primary>r", "F5"]);
        self.set_accels_for_action("win.shuffle-icons", &["<primary>r", "F5"]);
    }

    fn show_about_dialog(&self) {
        adw::AboutWindow::builder()
            .application_name(gettext("Symbolic Preview"))
            .modal(true)
            .version(VERSION)
            .comments(gettext("Symbolics Made Easy"))
            .website("https://gitlab.gnome.org/World/design/symbolic-preview")
            .developers(vec!["Bilal Elmoussaoui"])
            .artists(vec!["Jakub Steiner", "Tobias Bernard"])
            .translator_credits(gettext("translator-credits"))
            .application_icon(APP_ID)
            .license_type(gtk::License::Gpl30)
            .transient_for(&self.window())
            .build()
            .present();
    }

    pub fn run() -> glib::ExitCode {
        tracing::info!("Symbolic Preview ({})", APP_ID);
        tracing::info!("Version: {} ({})", VERSION, PROFILE);
        tracing::info!("Datadir: {}", PKGDATADIR);

        glib::Object::builder::<Self>()
            .property("application-id", APP_ID)
            .property("flags", gio::ApplicationFlags::HANDLES_OPEN)
            .property("resource-base-path", "/org/gnome/design/SymbolicPreview/")
            .build()
            .run()
    }
}
