//! A Rust client for [ComfyUI](https://github.com/comfyanonymous/ComfyUI).
//!
//! Build workflows with [`WorkflowGraph`], using the typed node definitions in
//! [`nodes`] (the `typed_nodes` feature, on by default) or dynamic nodes, then
//! queue them with [`Client::execute`]. That returns a stream of progress
//! [`Event`]s and the final outputs; the `websocket` feature adds live progress
//! and previews. The rest of the API - object info, history, the queue, models,
//! system stats, image upload - hangs off [`Client`].
//!
//! See the `examples` directory for runnable programs.
#![deny(missing_docs)]

mod constructor;

pub mod workflow;
pub use workflow::{Workflow, WorkflowGraph, WorkflowNodeId};

#[cfg(feature = "typed_nodes")]
#[rustfmt::skip]
pub mod nodes;

// This order is intentional as it is the order it will be displayed in Rustdoc.
pub mod queue;

pub mod execute;
pub use execute::{Event, Execution, NodeOutput};

pub mod history;

pub mod models;

pub mod object_info;

pub mod system;

pub mod upload;

pub mod error;
pub use error::ClientError;
use error::parse_response;

/// Result type for the client.
pub type Result<T> = std::result::Result<T, ClientError>;

/// An alias around `Vec<u8>` for raw bytes.
pub type OwnedBytes = Vec<u8>;

/// An alias around `Cow<'a, [u8]>` for borrowed bytes.
pub type BorrowedBytes<'a> = std::borrow::Cow<'a, [u8]>;

/// Client for the ComfyUI API.
#[derive(Clone)]
pub struct Client {
    api_base: String,
    client: reqwest::Client,
    client_id: String,
}
/// Functions for interacting with the ComfyUI API.
impl Client {
    /// The client ID for this client.
    ///
    /// This is sent with every queued prompt and used to correlate
    /// WebSocket events (see [`Client::execute`]) with this client.
    pub fn client_id(&self) -> &str {
        &self.client_id
    }

    /// Get a resource from the ComfyUI API.
    pub async fn get<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T> {
        parse_response(
            self.client
                .get(format!("{}/{}", self.api_base, path))
                .send()
                .await?,
        )
        .await
    }

    /// Post a JSON resource to the ComfyUI API without parsing the response.
    ///
    /// This does not do any validation of the response.
    pub async fn post_json_without_parse<Req: serde::Serialize>(
        &self,
        path: &str,
        body: &Req,
    ) -> Result<String> {
        Ok(self
            .client
            .post(format!("{}/{}", self.api_base, path))
            .json(body)
            .send()
            .await?
            .text()
            .await?)
    }

    /// Post a JSON resource to the ComfyUI API.
    pub async fn post_json<Req: serde::Serialize, Res: serde::de::DeserializeOwned>(
        &self,
        path: &str,
        body: &Req,
    ) -> Result<Res> {
        parse_response(
            self.client
                .post(format!("{}/{}", self.api_base, path))
                .json(body)
                .send()
                .await?,
        )
        .await
    }

    /// Post a multipart resource to the ComfyUI API.
    pub async fn post_multipart<Req: serde::Serialize, Res: serde::de::DeserializeOwned>(
        &self,
        path: &str,
        body: reqwest::multipart::Form,
    ) -> Result<Res> {
        parse_response(
            self.client
                .post(format!("{}/{}", self.api_base, path))
                .multipart(body)
                .send()
                .await?,
        )
        .await
    }
}
