/// A single query/path/body parameter declared on an API route.
#[derive(serde::Serialize, Clone)]
pub struct ApiParam {
    pub name: &'static str,
    /// Rust-like type hint: string | u32 | bool | i64 | json.
    pub kind: &'static str,
    /// "required" | "optional" | "default=<value>"
    pub constraint: &'static str,
    pub description: &'static str,
}

/// Static metadata for an HTTP endpoint.
///
/// Registered at link time via [`inventory::submit!`] — generated by
/// `#[onto_api(...)]` proc-macro attribute on each handler function.
/// Collected via [`inventory::iter::<ApiRouteEntry>()`].
#[derive(serde::Serialize, Clone)]
pub struct ApiRouteEntry {
    pub method: &'static str,
    pub path: &'static str,
    pub description: &'static str,
    /// Authentication required: "none" | "viewer" | "bearer" | "admin"
    pub auth: &'static str,
    /// Which actors typically call this endpoint.
    pub actors: &'static [&'static str],
    pub params: &'static [ApiParam],
    /// Semantic grouping tags (e.g. "graph", "federation", "describe").
    pub tags: &'static [&'static str],
    /// Non-empty when the endpoint is only compiled under a feature flag.
    pub feature: &'static str,
}

inventory::collect!(ApiRouteEntry);

/// Serialize all statically-registered [`ApiRouteEntry`] items to a
/// pretty-printed JSON array, sorted by path then method.
///
/// Intended for daemon binaries that expose a `--dump-api-catalog` flag: write
/// the output to `api-catalog.json` in the project root so the ontoref UI can
/// display the API surface of consumer projects that run as separate processes.
pub fn dump_catalog_json() -> String {
    let mut routes: Vec<&'static ApiRouteEntry> = inventory::iter::<ApiRouteEntry>().collect();
    routes.sort_by(|a, b| a.path.cmp(b.path).then(a.method.cmp(b.method)));
    serde_json::to_string_pretty(&routes).unwrap_or_else(|_| "[]".to_string())
}
