// Generated by RustGObjectGenerator
// This file is licensed under the same terms as the project it belongs to

use crate::i18n::gettext;
use crate::models::{DialogType, DistroboxSource, RootStore, ViewType};
use crate::widgets::TerminalComboRow;
use adw::prelude::*;
use adw::subclass::prelude::*;
use glib::Properties;
use glib::clone;
use gtk::{gio, glib};
use std::cell::RefCell;

mod imp {

    use super::*;

    #[derive(Properties, Default, gtk::CompositeTemplate)]
    #[properties(wrapper_type = super::WelcomeView)]
    #[template(file = "welcome_view.ui")]
    pub struct WelcomeView {
        #[property(get, set)]
        root_store: RefCell<RootStore>,

        #[property(get, set, nullable)]
        distrobox_error: RefCell<Option<String>>,
        #[property(get, set, nullable)]
        terminal_error: RefCell<Option<String>>,

        #[template_child]
        carousel: TemplateChild<adw::Carousel>,
        #[template_child]
        terminal_preferences_page: TemplateChild<adw::Clamp>,
        #[template_child]
        terminal_combo_row: TemplateChild<TerminalComboRow>,
        #[template_child]
        container_runtime_row: TemplateChild<adw::ActionRow>,
        #[template_child]
        distrobox_row: TemplateChild<adw::ActionRow>,
        #[template_child]
        runtime_status_spinner: TemplateChild<adw::Spinner>,
        #[template_child]
        distrobox_status_spinner: TemplateChild<adw::Spinner>,
        #[template_child]
        runtime_version_label: TemplateChild<gtk::Label>,
        #[template_child]
        distrobox_version_label: TemplateChild<gtk::Label>,
        #[template_child]
        continue_btn: TemplateChild<gtk::Button>,
        #[template_child]
        refresh_btn: TemplateChild<gtk::Button>,

        runtime_status_icon: RefCell<Option<gtk::Image>>,
        distrobox_status_icon: RefCell<Option<gtk::Image>>,
    }

    #[glib::derived_properties]
    impl ObjectImpl for WelcomeView {
        fn constructed(&self) {
            self.parent_constructed();
            let obj = self.obj();

            // Initialize status checks
            obj.connect_root_store_notify(|view| {
                view.imp().setup_status_checks();
            });

            // Setup actions
            self.setup_actions();
        }
    }

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

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

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

    impl WidgetImpl for WelcomeView {
        fn map(&self) {
            self.parent_map();

            // Whenever the welcome view becomes visible again, snap back to the
            // requirements page. The view can be re-shown after the user already
            // advanced to the terminal page (e.g. the distrobox check later
            // fails and the root store forces the view back to Welcome); without
            // this the carousel would still be parked on the terminal page
            // instead of the requirements the user needs to fix.
            let first_page = self.carousel.nth_page(0);
            self.carousel.scroll_to(&first_page, false);
        }
    }
    impl BinImpl for WelcomeView {}

    impl WelcomeView {
        fn setup_status_checks(&self) {
            let obj = self.obj();
            let root_store = obj.root_store();

            // Check container runtime
            root_store.container_runtime().refetch();
            root_store.container_runtime().connect_success(clone!(
                #[weak]
                obj,
                move |detected| {
                    obj.imp().update_runtime_status(Some(detected));
                }
            ));
            root_store.container_runtime().connect_error(clone!(
                #[weak]
                obj,
                move |_error| {
                    obj.imp().update_runtime_status(None);
                }
            ));

            // Check distrobox version
            root_store.distrobox_version().refetch();
            root_store.distrobox_version().connect_success(clone!(
                #[weak]
                obj,
                move |exe| {
                    obj.imp().update_distrobox_status(exe.as_ref());
                }
            ));
            root_store.distrobox_version().connect_error(clone!(
                #[weak]
                obj,
                move |_error| {
                    obj.imp().update_distrobox_status(None);
                }
            ));
        }

        fn update_runtime_status(
            &self,
            detected: Option<&crate::backends::container_runtime::DetectedRuntime>,
        ) {
            self.runtime_status_spinner.set_visible(false);

            // Reset the version label; it is only shown again once a fresh
            // version is available below. Without this, a failed re-check would
            // leave a stale version visible next to the "Not found" warning.
            self.runtime_version_label.set_text("");
            self.runtime_version_label.set_visible(false);

            // Remove old icon if exists
            if let Some(old_icon) = self.runtime_status_icon.take() {
                self.container_runtime_row.remove(&old_icon);
            }

            if let Some(detected) = detected {
                let icon = gtk::Image::from_icon_name("check-plain-symbolic");
                icon.add_css_class("success");
                self.container_runtime_row.add_prefix(&icon);
                self.runtime_status_icon.replace(Some(icon));

                let name = detected.runtime.name();
                let display_name = match name {
                    "podman" => "Podman",
                    "docker" => "Docker",
                    _ => name,
                };
                self.container_runtime_row.set_subtitle(&format!(
                    "{} {}",
                    display_name,
                    gettext("available")
                ));

                // The version was obtained during detection, so we can display
                // it synchronously without a second fetch.
                if !detected.version.is_empty() {
                    self.runtime_version_label.set_text(&detected.version);
                    self.runtime_version_label.set_visible(true);
                }
            } else {
                let icon = gtk::Image::from_icon_name("dialog-warning-symbolic");
                icon.add_css_class("warning");
                self.container_runtime_row.add_prefix(&icon);
                self.runtime_status_icon.replace(Some(icon));
                self.container_runtime_row
                    .set_subtitle(&gettext("Not found - Please install Podman or Docker"));
            }

            self.update_continue_button();
        }

        fn update_distrobox_status(&self, exe: Option<&crate::models::DistroboxExecutable>) {
            self.distrobox_status_spinner.set_visible(false);

            // Reset the version label; it is only shown again once a fresh
            // version is available below.
            self.distrobox_version_label.set_text("");
            self.distrobox_version_label.set_visible(false);

            // Remove old icon if exists
            if let Some(old_icon) = self.distrobox_status_icon.take() {
                self.distrobox_row.remove(&old_icon);
            }

            if let Some(exe) = exe {
                let icon = gtk::Image::from_icon_name("check-plain-symbolic");
                icon.add_css_class("success");
                self.distrobox_row.add_prefix(&icon);
                self.distrobox_status_icon.replace(Some(icon));

                let source_label = if exe.is_bundled() {
                    gettext("Bundled version")
                } else {
                    gettext("Host version")
                };

                self.distrobox_row.set_subtitle(&format!(
                    "{} {}",
                    source_label,
                    gettext("available")
                ));
                self.distrobox_version_label.set_text(exe.version());
                self.distrobox_version_label.set_visible(true);
            } else {
                let icon = gtk::Image::from_icon_name("dialog-warning-symbolic");
                icon.add_css_class("warning");
                self.distrobox_row.add_prefix(&icon);
                self.distrobox_status_icon.replace(Some(icon));
                self.distrobox_row.set_subtitle(&gettext(
                    "Not found - Install from system or use bundled version",
                ));
            }

            self.update_continue_button();
        }

        fn update_continue_button(&self) {
            // Enable the continue button only when both requirements are
            // currently satisfied. See `requirements_met` for why a query being
            // `is_success()` is not enough on its own.
            self.continue_btn.set_sensitive(self.requirements_met());
            // Re-enable refresh button only once both checks have completed
            // (both spinners hidden), avoiding a premature re-enable after just
            // the first check finishes.
            let checks_done = !self.runtime_status_spinner.get_visible()
                && !self.distrobox_status_spinner.get_visible();
            if checks_done {
                self.refresh_btn.set_sensitive(true);
            }
        }

        /// Whether both requirements are currently satisfied.
        ///
        /// A query being `is_success()` only reports the outcome of its last
        /// *completed* fetch, which is retained across a subsequent refetch. So
        /// during a refresh a previously-successful query still reports success
        /// until the new fetch finishes. We therefore also require that no check
        /// is in flight. For distrobox, we additionally check that the
        /// executable is actually available (the query can succeed with None
        /// when distrobox is not installed).
        fn requirements_met(&self) -> bool {
            let root_store = self.obj().root_store();
            let runtime = root_store.container_runtime();
            let distrobox = root_store.distrobox_version();
            runtime.is_success()
                && distrobox.is_success()
                && distrobox.data().is_some_and(|d| d.is_some())
                && !runtime.is_loading()
                && !distrobox.is_loading()
        }

        fn setup_actions(&self) {
            let obj = self.obj();
            let action_group = gio::SimpleActionGroup::new();

            // Create the "use-bundled-distrobox" action
            let use_bundled_action = gio::SimpleAction::new("use-bundled-distrobox", None);
            use_bundled_action.connect_activate(clone!(
                #[weak]
                obj,
                move |_, _| {
                    // Create a temporary button to pass to the handler
                    let button = gtk::Button::new();
                    obj.imp().use_bundled_version(&button);
                }
            ));
            action_group.add_action(&use_bundled_action);

            obj.insert_action_group("welcome", Some(&action_group));
        }
    }

    #[gtk::template_callbacks]
    impl WelcomeView {
        #[template_callback]
        fn refresh_requirements(&self, _: &gtk::Button) {
            let obj = self.obj();
            let root_store = obj.root_store();

            // Show spinners
            self.runtime_status_spinner.set_visible(true);
            self.distrobox_status_spinner.set_visible(true);

            // Clear icons
            if let Some(old_icon) = self.runtime_status_icon.take() {
                self.container_runtime_row.remove(&old_icon);
            }
            if let Some(old_icon) = self.distrobox_status_icon.take() {
                self.distrobox_row.remove(&old_icon);
            }

            self.container_runtime_row
                .set_subtitle(&gettext("Checking…"));
            self.distrobox_row.set_subtitle(&gettext("Checking…"));
            self.runtime_version_label.set_visible(false);
            self.distrobox_version_label.set_visible(false);

            // Disable continue button during refresh
            self.continue_btn.set_sensitive(false);
            self.refresh_btn.set_sensitive(false);

            // Trigger refetch
            root_store.container_runtime().refetch();
            root_store.distrobox_version().refetch();
        }

        #[template_callback]
        fn continue_to_terminal_page(&self, _: &gtk::Button) {
            let obj = self.obj();
            let root_store = obj.root_store();

            // Only proceed if both requirements are currently satisfied (latest
            // check succeeded and no re-check is in flight).
            if self.requirements_met() {
                // Load containers and proceed to terminal selection page
                root_store.load_containers();
                self.carousel
                    .scroll_to(&*self.terminal_preferences_page, true);
            }
        }
        #[template_callback]
        fn continue_to_app(&self, _: &gtk::Button) {
            let obj = self.obj().clone();
            if obj.root_store().selected_terminal().is_some() {
                glib::MainContext::ref_thread_default().spawn_local(async move {
                    match obj.root_store().validate_terminal().await {
                        Ok(_) => {
                            debug_assert!(
                                obj.root_store()
                                    .distrobox_version()
                                    .data()
                                    .is_some_and(|d| d.is_some()),
                                "cannot enter Main without a resolved distrobox executable"
                            );
                            obj.root_store().set_current_view(ViewType::Main);
                        }
                        Err(err) => {
                            obj.set_terminal_error(Some(format!("{}", err)));
                        }
                    }
                });
            }
        }
        #[template_callback]
        fn use_bundled_version(&self, btn: &gtk::Button) {
            let obj = self.obj();

            // If a bundle (stable or legacy) already exists, no download is
            // needed: resolve the existing executable and switch to it.
            if obj.root_store().has_bundled_distrobox() {
                btn.set_child(Some(&gtk::Label::new(Some(&gettext(
                    "Use Bundled Version",
                )))));
                btn.set_sensitive(true);
                obj.root_store()
                    .set_distrobox_source(DistroboxSource::Bundled);
                obj.root_store().bundled_distrobox_version().refetch();
                return;
            }

            // Show spinner in button
            let spinner = adw::Spinner::new();
            btn.set_child(Some(&spinner));
            btn.set_sensitive(false);

            // Directly trigger the download and get the task
            let task = obj.root_store().download_distrobox();

            // Connect to the task status changes
            task.connect_status_notify(clone!(
                #[weak]
                obj,
                #[strong]
                btn,
                move |task| {
                    if task.is_successful() {
                        obj.imp()
                            .carousel
                            .scroll_to(&*obj.imp().terminal_preferences_page, true);
                    } else if task.is_failed() {
                        btn.set_child(Some(&gtk::Label::new(Some(&gettext(
                            "Use Bundled Version",
                        )))));
                        btn.set_sensitive(true);
                        obj.set_distrobox_error(Some(gettext("Download failed")));
                    }
                }
            ));

            // Open task manager dialog from welcome view
            obj.root_store().set_current_dialog(DialogType::TaskManager);

            // Set the preference for future launches
            obj.root_store()
                .set_distrobox_source(DistroboxSource::Bundled);
        }
    }
}

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