//! The AdwApplication subclass — owns the lifetime of the UI
//! process. Single-window app: closing the window EXITS the UI
//! process. The system-tray icon belongs to the agent (a separate
//! process), which keeps running. Removed the in-UI tray in v0.3.3
//! because two trays for one product was visually noisy and the
//! agent's tray is sufficient for re-opening the window.

use crate::dbus::AgentClient;
use crate::dbus_signals::AgentEvent;
use crate::window::ZbplWindow;
use adw::prelude::*;
use adw::subclass::prelude::*;
use gtk::glib;
use std::cell::OnceCell;
use std::rc::Rc;

mod imp {
    use super::*;

    #[derive(Debug, Default)]
    pub struct ZbplApplication {
        pub agent: OnceCell<Rc<AgentClient>>,
        pub window: OnceCell<ZbplWindow>,
    }

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

    impl ObjectImpl for ZbplApplication {}
    impl ApplicationImpl for ZbplApplication {
        fn activate(&self) {
            self.parent_activate();
            let app = self.obj();
            // Lazily build the window the first time the app is
            // activated. Subsequent activations just present it.
            let window = self.window.get_or_init(|| {
                let agent = self.agent.get().expect("agent set in main").clone();
                ZbplWindow::new(app.upcast_ref::<adw::Application>(), agent)
            });
            window.present();
        }
    }
    impl GtkApplicationImpl for ZbplApplication {}
    impl AdwApplicationImpl for ZbplApplication {}
}

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

impl ZbplApplication {
    pub fn new(app_id: &str, agent: Rc<AgentClient>) -> Self {
        let app: Self = glib::Object::builder()
            .property("application-id", app_id)
            // Default flags: GApplication tracks open windows and
            // exits when the last is closed. We DON'T `hold()` it
            // any more — closing the window is meant to fully exit
            // the UI process so the user sees a clean restart on
            // next launch via the agent's tray.
            .property("flags", gio::ApplicationFlags::FLAGS_NONE)
            .build();
        app.imp().agent.set(agent).ok();
        app.set_resource_base_path(Some("/com/printplatformpro/BrowserAgent"));
        app
    }

    /// Drain D-Bus AgentEvent signals (OriginPendingApproval today)
    /// off the subscriber thread's mpsc channel. Same 100 ms cadence
    /// as the tray drain. Each event pops a native AdwAlertDialog
    /// that calls back into the agent over D-Bus on user choice.
    pub fn install_signal_drain(&self, rx: std::sync::mpsc::Receiver<AgentEvent>) {
        let weak = self.downgrade();
        glib::timeout_add_local(std::time::Duration::from_millis(100), move || {
            let Some(app) = weak.upgrade() else {
                return glib::ControlFlow::Break;
            };
            while let Ok(evt) = rx.try_recv() {
                match evt {
                    AgentEvent::OriginPending { origin, token: _ } => {
                        app.show_approval_dialog(origin);
                    }
                }
            }
            glib::ControlFlow::Continue
        });
    }

    /// Pop the native approval dialog for an unknown origin.
    /// Activates the main window first (raising it from the tray
    /// if it was closed) so the dialog has a parent that's visible
    /// to the user — orphan dialogs are jarring on Wayland and
    /// genuinely confusing in tiling WMs.
    fn show_approval_dialog(&self, origin: String) {
        self.activate();
        let imp = self.imp();
        let Some(window) = imp.window.get() else { return };
        let Some(agent) = imp.agent.get() else { return };

        let dialog = adw::AlertDialog::new(
            Some("Authorise printing?"),
            Some(&format!(
                "{} is asking to print to your local Zebra printer. \
                Approve to add this site to your allowlist; you can revoke any time from the Approved sites page.",
                origin
            )),
        );
        dialog.add_response("deny", "Deny");
        dialog.add_response("approve", "Approve");
        dialog.set_response_appearance("approve", adw::ResponseAppearance::Suggested);
        dialog.set_default_response(Some("approve"));
        dialog.set_close_response("deny");

        let agent_for_choice = agent.clone();
        let window_for_refresh = window.clone();
        dialog.connect_response(None, move |dlg, response| {
            if response == "approve" {
                if let Err(e) = agent_for_choice.approve(&origin) {
                    tracing::error!("approve({origin}) via dialog: {e:?}");
                    // Failure surfaces in the agent log; the next
                    // print attempt from this origin will simply
                    // fire OriginPendingApproval again.
                }
                window_for_refresh.refresh_all();
            }
            dlg.close();
        });
        dialog.present(Some(window));
    }
}

use gtk::gio;
