//! Subscribes to the agent's `OriginPendingApproval` D-Bus signal
//! and forwards each event to the GTK main thread for prompt
//! display.
//!
//! Implementation: a dedicated OS thread holds a sync zbus
//! `Connection` + `Proxy::receive_signal` iterator. Each signal
//! emitted by the agent yields one message; we deserialise the
//! body, build an `AgentEvent`, and push it through an
//! `mpsc::Sender`. The GTK side drains on a 100 ms timeout, same
//! pattern as the tray channel.

use std::sync::mpsc;

use anyhow::{Context, Result};

const BUS_NAME: &str = "com.printplatformpro.BrowserAgent";
const OBJECT_PATH: &str = "/com/printplatformpro/BrowserAgent";
const INTERFACE: &str = "com.printplatformpro.BrowserAgent1";
const SIGNAL_NAME: &str = "OriginPendingApproval";

/// Events the UI side of the channel can receive. Adding a new
/// signal subscription = one new variant + one new arm in
/// application.rs's drain loop.
#[derive(Debug, Clone)]
pub enum AgentEvent {
    /// The agent saw a request from an origin not on the
    /// allowlist. UI should pop an approval dialog. The token is
    /// echoed only for parity with the HTML flow (the D-Bus
    /// Approve(origin) call doesn't need it).
    OriginPending { origin: String, token: String },
}

/// Spawn the subscriber thread. Returns the receiver the GTK main
/// thread should drain via a GLib timeout.
///
/// Failure modes (initial connect, missing service): returned as
/// `Err`. The application surfaces these as a "Disconnected"
/// status indicator rather than crashing.
pub fn spawn() -> Result<mpsc::Receiver<AgentEvent>> {
    // Test-connect on this thread so an immediate failure surfaces
    // synchronously to the caller. The actual long-lived
    // connection is opened inside the worker thread for clean
    // ownership — moving zbus::blocking::Proxy across threads is
    // doable but the iterator's lifetime is awkward to express in
    // a function signature, so we keep everything thread-local.
    {
        let test_conn = zbus::blocking::Connection::session().context("session bus")?;
        let _proxy = zbus::blocking::Proxy::new(&test_conn, BUS_NAME, OBJECT_PATH, INTERFACE)
            .context("agent signal proxy")?;
    }

    let (tx, rx) = mpsc::channel();
    std::thread::Builder::new()
        .name("zbpl-dbus-signals".into())
        .spawn(move || {
            if let Err(e) = run(tx) {
                tracing::error!("dbus signal thread exited: {e:?}");
            }
        })?;

    Ok(rx)
}

fn run(tx: mpsc::Sender<AgentEvent>) -> Result<()> {
    let conn = zbus::blocking::Connection::session().context("session bus (worker)")?;
    let proxy = zbus::blocking::Proxy::new(&conn, BUS_NAME, OBJECT_PATH, INTERFACE)
        .context("agent signal proxy (worker)")?;
    let signals = proxy
        .receive_signal(SIGNAL_NAME)
        .context("subscribe OriginPendingApproval")?;

    for msg in signals {
        match msg.body().deserialize::<(String, String)>() {
            Ok((origin, token)) => {
                tracing::info!("agent signalled OriginPendingApproval origin={origin}");
                if tx
                    .send(AgentEvent::OriginPending { origin, token })
                    .is_err()
                {
                    // GTK side is gone — wind down.
                    break;
                }
            }
            Err(e) => {
                tracing::warn!("decode OriginPendingApproval body: {e:?}");
            }
        }
    }

    drop(proxy);
    drop(conn);
    Ok(())
}
