use std::{path::PathBuf, sync::OnceLock};

use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{
    gio,
    glib::{self, clone},
};
use search_provider::{IconData, ResultID, ResultMeta, SearchProvider, SearchProviderImpl};

use crate::{
    config,
    models::IconsModel,
    widgets::{ExportDialog, Window},
};

#[doc(hidden)]
mod imp {
    use std::cell::RefCell;

    use glib::WeakRef;

    use super::*;

    pub struct Application {
        pub window: RefCell<Option<WeakRef<Window>>>,
        pub model: IconsModel,
        pub settings: gio::Settings,
        pub search_provider: RefCell<Option<SearchProvider<super::Application>>>,
    }

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

        fn new() -> Self {
            let settings = gio::Settings::new(config::APP_ID);
            Self {
                settings,
                model: IconsModel::default(),
                window: RefCell::default(),
                search_provider: RefCell::default(),
            }
        }
    }
    impl ObjectImpl for Application {}
    impl ApplicationImpl for Application {
        fn startup(&self) {
            self.parent_startup();
            let application = self.obj();

            let quit = gio::ActionEntry::builder("quit")
                .activate(|app: &Self::Type, _, _| app.quit())
                .build();
            let about = gio::ActionEntry::builder("about")
                .activate(|app: &Self::Type, _, _| {
                    let window = app.active_window().unwrap();
                    adw::AboutDialog::builder()
                        .application_icon(config::APP_ID)
                        .application_name(gettext("Icon Library"))
                        .developer_name("Bilal Elmoussaoui")
                        .version(config::VERSION)
                        .website("https://gitlab.gnome.org/World/design/icon-library")
                        .issue_url("https://gitlab.gnome.org/World/design/icon-library/-/issues")
                        .developers(vec!["Bilal Elmoussaoui"])
                        .designers(vec!["Jakub Steiner", "Tobias Bernard"])
                        .translator_credits(gettext("translator-credits"))
                        .license_type(gtk::License::Gpl30)
                        .build()
                        .present(Some(&window));
                })
                .build();

            application.add_action_entries([quit, about]);

            let dark_mode_action = self.settings.create_action("dark-mode");
            application.add_action(&dark_mode_action);

            self.settings.connect_changed(
                Some("dark-mode"),
                clone!(
                    #[weak]
                    application,
                    move |_, _| application.update_color_scheme()
                ),
            );
            application.update_color_scheme();

            let search_provider_path = config::OBJECT_PATH;
            let search_provider_name = format!("{}.SearchProvider", config::APP_ID);

            // Init icon cache used for drag n drop.
            std::fs::create_dir_all(Self::Type::icons_cache())
                .unwrap_or_else(|err| log::debug!("Could not create cache directory: {err}"));

            // Accels
            application.set_accels_for_action("app.dark-mode", &["<primary>T"]);
            application.set_accels_for_action("app.about", &["<primary>comma"]);
            application.set_accels_for_action("win.search", &["<primary>f"]);
            application.set_accels_for_action("app.quit", &["<primary>q"]);
            application.set_accels_for_action("window.close", &["<primary>w"]);

            glib::spawn_future_local(clone!(
                #[weak]
                application,
                async move {
                    match SearchProvider::new(
                        application.clone(),
                        search_provider_name,
                        search_provider_path,
                    )
                    .await
                    {
                        Ok(search_provider) => {
                            application
                                .imp()
                                .search_provider
                                .replace(Some(search_provider));
                        }
                        Err(err) => log::debug!("Could not start search provider: {err}"),
                    };
                }
            ));

            gtk::Window::set_default_icon_name(config::APP_ID);
        }

        fn activate(&self) {
            self.parent_activate();
            self.obj().window().present();
        }
    }
    impl GtkApplicationImpl for Application {}
    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 {
    pub fn icons_cache() -> &'static PathBuf {
        pub static CACHE_PATH: OnceLock<PathBuf> = OnceLock::new();

        CACHE_PATH.get_or_init(|| glib::user_cache_dir().join("icon-library").join("icons"))
    }

    pub fn run() -> glib::ExitCode {
        log::info!("Icon Library({})", config::APP_ID);
        log::info!("Version: {} ({})", config::VERSION, config::PROFILE);
        log::info!("Datadir: {}", config::PKGDATADIR);

        glib::Object::builder::<Self>()
            .property("application-id", config::APP_ID)
            .property("resource-base-path", "/org/gnome/design/IconLibrary")
            .build()
            .run()
    }

    fn window(&self) -> Window {
        let imp = self.imp();

        if let Some(ref win) = *imp.window.borrow() {
            return win.upgrade().unwrap();
        }

        let window = Window::new(&imp.model, self);
        self.add_window(&window);
        imp.window.replace(Some(window.downgrade()));
        window
    }

    fn update_color_scheme(&self) {
        let manager = adw::StyleManager::default();

        if !manager.system_supports_color_schemes() {
            let color_scheme = if self.imp().settings.boolean("dark-mode") {
                adw::ColorScheme::PreferDark
            } else {
                adw::ColorScheme::PreferLight
            };
            manager.set_color_scheme(color_scheme);
        }
    }
}

impl SearchProviderImpl for Application {
    fn activate_result(&self, identifier: ResultID, _terms: &[String], timestamp: u32) {
        let window = self.window();

        // Export the icon
        if let Some(icon) = self.imp().model.get_icon_byname(&identifier) {
            window.present_with_time(timestamp);
            let export_dialog = ExportDialog::new(icon);
            export_dialog.present(Some(&window));
        }
    }

    fn initial_result_set(&self, terms: &[String]) -> Vec<String> {
        let mut icon_names = self.imp().model.search(terms);
        icon_names.sort();
        icon_names
    }

    fn result_metas(&self, identifiers: &[ResultID]) -> Vec<ResultMeta> {
        let model = &self.imp().model;
        identifiers
            .iter()
            .map(clone!(
                #[weak]
                model,
                #[upgrade_or_panic]
                move |sp_id| {
                    model.get_icon_byname(sp_id).map(|icon| {
                        if let Some(texture) = icon.texture() {
                            let icon_data = IconData::from(&texture);
                            ResultMeta::builder(sp_id.to_string(), sp_id)
                                .clipboard_text(sp_id)
                                .icon_data(icon_data)
                                .build()
                        } else {
                            ResultMeta::builder(sp_id.to_string(), sp_id)
                                .clipboard_text(sp_id)
                                .gicon(sp_id)
                                .build()
                        }
                    })
                }
            ))
            .flatten()
            .collect::<Vec<ResultMeta>>()
    }
}
