//! Proxies for the daemon's `declagents.*` namespace (in-daemon declarative
//! agents). Thin [`DaemonClient`] calls.

use serde_json::{json, Value};

use crate::proxy::DaemonClient;

fn to_string(v: Value) -> Result<String, String> {
    serde_json::to_string(&v).map_err(|e| e.to_string())
}

/// `declagents.list` — registered declarative agents.
pub async fn list(client: &DaemonClient) -> Result<String, String> {
    client
        .call("declagents.list", json!({}))
        .await
        .and_then(to_string)
}

/// `declagents.get` — one agent's spec.
pub async fn get(client: &DaemonClient, id: &str) -> Result<String, String> {
    client
        .call("declagents.get", json!({ "id": id }))
        .await
        .and_then(to_string)
}

/// `declagents.remove` — unregister an agent.
pub async fn remove(client: &DaemonClient, id: &str) -> Result<String, String> {
    client
        .call("declagents.remove", json!({ "id": id }))
        .await
        .and_then(to_string)
}

/// `declagents.set_enabled` — enable/disable an agent.
pub async fn set_enabled(client: &DaemonClient, id: &str, enabled: bool) -> Result<String, String> {
    client
        .call(
            "declagents.set_enabled",
            json!({ "id": id, "enabled": enabled }),
        )
        .await
        .and_then(to_string)
}

/// `declagents.invoke` — run an agent on an input, in-daemon. Returns
/// `{ output, turns, tool_calls, error? }`.
pub async fn invoke(client: &DaemonClient, id: &str, input: &str) -> Result<String, String> {
    client
        .call("declagents.invoke", json!({ "id": id, "input": input }))
        .await
        .and_then(to_string)
}

/// `declagents.route` — pick the agent whose capability best matches `need`
/// (capability-similarity routing). Returns `{ chosen, candidates, next_visited,
/// invoked, result? }`. With `invoke: true`, the top agent is run on `need` and
/// its `{ output, turns, tool_calls, error? }` lands in `result`.
///
/// This binding covers the network-entry case. Multi-hop Forward chaining (the
/// `from`/`visited` params) is WS-only — drive it over the raw JSON-RPC method.
pub async fn route(client: &DaemonClient, need: &str, invoke: bool) -> Result<String, String> {
    client
        .call(
            "declagents.route",
            json!({ "need": need, "invoke": invoke }),
        )
        .await
        .and_then(to_string)
}

/// `declagents.route_split` — decompose `need` into subtasks and route each to
/// its best-matching agent. Returns `{ subtasks: [{ subtask, chosen, score,
/// result? }], count, invoked }`. `max_subtasks` caps the split (clamped to
/// [1, 10]; None = default 5). With `invoke: true`, each subtask's chosen agent
/// runs on that subtask.
pub async fn route_split(
    client: &DaemonClient,
    need: &str,
    invoke: bool,
    max_subtasks: Option<u32>,
    decomposition_mode: Option<&str>,
    sad_hints: Option<u32>,
    sad_iterations: Option<u32>,
    sad_convergence_jaccard: Option<f64>,
) -> Result<String, String> {
    let mut params = json!({ "need": need, "invoke": invoke });
    if let Some(m) = max_subtasks {
        params["max_subtasks"] = json!(m);
    }
    if let Some(mode) = decomposition_mode {
        params["decomposition_mode"] = json!(mode);
    }
    if let Some(h) = sad_hints {
        params["sad_hints"] = json!(h);
    }
    if let Some(i) = sad_iterations {
        params["sad_iterations"] = json!(i);
    }
    if let Some(j) = sad_convergence_jaccard {
        params["sad_convergence_jaccard"] = json!(j);
    }
    client
        .call("declagents.route_split", params)
        .await
        .and_then(to_string)
}

/// `declagents.routing_stats` — read-only view of the learned routing topology.
/// Returns `{ agents: { id: { successes, failures, ema_success_rate } }, edges:
/// { from: { to: weight } } }`.
pub async fn routing_stats(client: &DaemonClient) -> Result<String, String> {
    client
        .call("declagents.routing_stats", json!({}))
        .await
        .and_then(to_string)
}
