//! Thin client for the agent's `/__demos` endpoint.
//!
//! Until this revision the Linux UI carried its own ~1000-line copy
//! of every demo's ZPL renderer alongside the Go agent's parallel
//! implementation in `internal/demos/`. macOS already fetched over
//! HTTP; this file converges Linux on the same single source of
//! truth so a spec tweak (label layout, font size, embedded asset)
//! only has to land in one place. The Go side is the canonical
//! one because it's what the platform's other clients
//! (Mac/Windows native apps, web SDK) already speak to.
//!
//! `Demo` and `fetch_*` are deliberately blocking — every call lands
//! on `127.0.0.1:9100` and completes in single-digit ms. Call from a
//! worker thread if you can't afford that on the UI loop.
//!
//! On agent disconnect (HTTP listener down or process not running),
//! both functions return an error; callers surface this as an
//! actionable toast rather than hiding the demo list silently.

use anyhow::{anyhow, Context, Result};
use serde::Deserialize;
use std::time::Duration;

const AGENT_BASE_URL: &str = "http://127.0.0.1:9100";
const FETCH_TIMEOUT: Duration = Duration::from_secs(5);

/// One demo entry from the agent's catalogue. Heap-allocated
/// `String`s because the data comes from JSON; the previous
/// `&'static str` shape would have forced a leak-to-static dance
/// at the boundary for zero real benefit.
#[derive(Debug, Clone, Deserialize)]
pub struct Demo {
    pub id: String,
    pub title: String,
    pub description: String,
    /// The agent emits camelCase JSON; renamed for ergonomic
    /// snake_case in Rust callers.
    #[serde(rename = "labelSize")]
    pub label_size: String,
}

#[derive(Debug, Deserialize)]
struct DemoListResponse {
    demos: Vec<Demo>,
}

/// Fetch the demo catalogue from the agent. Synchronous — blocks for
/// up to `FETCH_TIMEOUT` before erroring out.
pub fn fetch_all() -> Result<Vec<Demo>> {
    let url = format!("{AGENT_BASE_URL}/__demos");
    let resp = ureq::get(&url)
        .timeout(FETCH_TIMEOUT)
        .call()
        .with_context(|| format!("GET {url}"))?
        .into_json::<DemoListResponse>()
        .context("decode demo list")?;
    Ok(resp.demos)
}

/// Fetch the ZPL for one demo at the given dpi. The agent returns
/// raw `text/plain` ZPL ready to pipe straight into `PrintZpl`.
pub fn fetch_zpl(id: &str, dpi: u32) -> Result<String> {
    if id.is_empty() {
        return Err(anyhow!("demo id is empty"));
    }
    let url = format!("{AGENT_BASE_URL}/__demos/{id}?dpi={dpi}");
    let zpl = ureq::get(&url)
        .timeout(FETCH_TIMEOUT)
        .call()
        .with_context(|| format!("GET {url}"))?
        .into_string()
        .context("read demo ZPL")?;
    Ok(zpl)
}
