// Code generated by TofuPilot SDK Generator. DO NOT EDIT.

use crate::client::TofuPilot;
use crate::error::{Error, Result};
use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};

/// Percent-encode set matching RFC 3986 unreserved characters.
const URI_COMPONENT: &AsciiSet = &NON_ALPHANUMERIC
    .remove(b'-')
    .remove(b'.')
    .remove(b'_')
    .remove(b'~');
use crate::types::*;

/// Client for the Units API.
#[derive(Debug, Clone)]
pub struct UnitsClient<'a> {
    pub(crate) client: &'a TofuPilot,
}

impl<'a> UnitsClient<'a> {
    pub(crate) fn new(client: &'a TofuPilot) -> Self {
        Self { client }
    }

    /// List and filter units
    ///
    /// Retrieve a paginated list of units with filtering by serial number, part number, and batch. Uses cursor-based pagination for efficient large dataset traversal.
    pub fn list(&self) -> ListBuilder<'a> {
        ListBuilder::new(self.client)
    }

    /// Create unit
    ///
    /// Create a new unit with a serial number and link it to a part revision. Units represent individual hardware items tracked for manufacturing traceability.
    pub fn create(&self) -> CreateBuilder<'a> {
        CreateBuilder::new(self.client)
    }

    /// Delete units
    ///
    /// Permanently delete units by serial number. This action will remove all nested elements and relationships associated with the units.
    pub fn delete(&self) -> DeleteBuilder<'a> {
        DeleteBuilder::new(self.client)
    }

    /// Get unit
    ///
    /// Retrieve a single unit by its serial number. Returns comprehensive unit data including part information, parent/child relationships, and test run history.
    pub fn get(&self) -> GetBuilder<'a> {
        GetBuilder::new(self.client)
    }

    /// Update unit
    ///
    /// Update unit properties including serial number, part revision, batch assignment, and file attachments with case-insensitive matching.
    pub fn update(&self) -> UpdateBuilder<'a> {
        UpdateBuilder::new(self.client)
    }

    /// Add sub-unit
    ///
    /// Add a sub-unit to a parent unit to track component assemblies and multi-level hardware traceability.
    pub fn add_child(&self) -> AddChildBuilder<'a> {
        AddChildBuilder::new(self.client)
    }

    /// Remove sub-unit
    ///
    /// Remove a sub-unit relationship from a parent unit by serial number. Only unlinks the parent-child relationship; neither unit is deleted from the system.
    pub fn remove_child(&self) -> RemoveChildBuilder<'a> {
        RemoveChildBuilder::new(self.client)
    }

    /// Attach file to unit
    ///
    /// Create an attachment linked to a unit and get a temporary pre-signed URL. Upload the file to the URL with a PUT request to complete the attachment.
    pub fn create_attachment(&self) -> CreateAttachmentBuilder<'a> {
        CreateAttachmentBuilder::new(self.client)
    }

    /// Delete unit attachments
    ///
    /// Delete attachments from a unit by their IDs. Removes the files from storage and unlinks them from the unit.
    pub fn delete_attachment(&self) -> DeleteAttachmentBuilder<'a> {
        DeleteAttachmentBuilder::new(self.client)
    }

}

// ---------------------------------------------------------------------------
// ListBuilder
// ---------------------------------------------------------------------------

/// Builder for [`UnitsClient::list`].
///
/// # Example
///
/// ```no_run
/// use tofupilot::TofuPilot;
///
/// # #[tokio::main]
/// # async fn main() -> tofupilot::Result<()> {
/// let client = TofuPilot::new("your-api-key");
/// let response = client.units().list()
///     .send()
///     .await?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug)]
pub struct ListBuilder<'a> {
    client: &'a TofuPilot,
    search_query: Option<String>,
    ids: Option<Vec<String>>,
    serial_numbers: Option<Vec<String>>,
    part_numbers: Option<Vec<String>>,
    revision_numbers: Option<Vec<String>>,
    batch_numbers: Option<Vec<String>>,
    procedure_ids: Option<Vec<String>>,
    outcomes: Option<Vec<Outcome>>,
    started_after: Option<chrono::DateTime<chrono::Utc>>,
    started_before: Option<chrono::DateTime<chrono::Utc>>,
    latest_only: Option<bool>,
    run_count_min: Option<i64>,
    run_count_max: Option<i64>,
    created_after: Option<chrono::DateTime<chrono::Utc>>,
    created_before: Option<chrono::DateTime<chrono::Utc>>,
    created_by_user_ids: Option<Vec<String>>,
    created_by_station_ids: Option<Vec<String>>,
    exclude_units_with_parent: Option<bool>,
    samples: Option<Vec<ListSample>>,
    limit: Option<i64>,
    cursor: Option<i64>,
    sort_by: Option<UnitListSortBy>,
    sort_order: Option<ListSortOrder>,
    metadata: Option<serde_json::Value>,
    include_metadata: Option<bool>,
    server_url: Option<String>,
    timeout: Option<std::time::Duration>,
}

impl<'a> ListBuilder<'a> {
    pub(crate) fn new(client: &'a TofuPilot) -> Self {
        Self {
            client,
            search_query: None,
            ids: None,
            serial_numbers: None,
            part_numbers: None,
            revision_numbers: None,
            batch_numbers: None,
            procedure_ids: None,
            outcomes: None,
            started_after: None,
            started_before: None,
            latest_only: None,
            run_count_min: None,
            run_count_max: None,
            created_after: None,
            created_before: None,
            created_by_user_ids: None,
            created_by_station_ids: None,
            exclude_units_with_parent: None,
            samples: None,
            limit: None,
            cursor: None,
            sort_by: None,
            sort_order: None,
            metadata: None,
            include_metadata: None,
            server_url: None,
            timeout: None,
        }
    }

    /// Set the `search_query` query parameter.
    pub fn search_query(mut self, value: impl Into<String>) -> Self {
        self.search_query = Some(value.into());
        self
    }

    /// Set the `ids` query parameter.
    pub fn ids(mut self, value: impl Into<Vec<String>>) -> Self {
        self.ids = Some(value.into());
        self
    }

    /// Set the `serial_numbers` query parameter.
    pub fn serial_numbers(mut self, value: impl Into<Vec<String>>) -> Self {
        self.serial_numbers = Some(value.into());
        self
    }

    /// Set the `part_numbers` query parameter.
    pub fn part_numbers(mut self, value: impl Into<Vec<String>>) -> Self {
        self.part_numbers = Some(value.into());
        self
    }

    /// Set the `revision_numbers` query parameter.
    pub fn revision_numbers(mut self, value: impl Into<Vec<String>>) -> Self {
        self.revision_numbers = Some(value.into());
        self
    }

    /// Set the `batch_numbers` query parameter.
    pub fn batch_numbers(mut self, value: impl Into<Vec<String>>) -> Self {
        self.batch_numbers = Some(value.into());
        self
    }

    /// Set the `procedure_ids` query parameter.
    pub fn procedure_ids(mut self, value: impl Into<Vec<String>>) -> Self {
        self.procedure_ids = Some(value.into());
        self
    }

    /// Set the `outcomes` query parameter.
    pub fn outcomes(mut self, value: impl Into<Vec<Outcome>>) -> Self {
        self.outcomes = Some(value.into());
        self
    }

    /// Set the `started_after` query parameter.
    pub fn started_after(mut self, value: impl Into<chrono::DateTime<chrono::Utc>>) -> Self {
        self.started_after = Some(value.into());
        self
    }

    /// Set the `started_before` query parameter.
    pub fn started_before(mut self, value: impl Into<chrono::DateTime<chrono::Utc>>) -> Self {
        self.started_before = Some(value.into());
        self
    }

    /// Set the `latest_only` query parameter.
    pub fn latest_only(mut self, value: impl Into<bool>) -> Self {
        self.latest_only = Some(value.into());
        self
    }

    /// Set the `run_count_min` query parameter.
    pub fn run_count_min(mut self, value: impl Into<i64>) -> Self {
        self.run_count_min = Some(value.into());
        self
    }

    /// Set the `run_count_max` query parameter.
    pub fn run_count_max(mut self, value: impl Into<i64>) -> Self {
        self.run_count_max = Some(value.into());
        self
    }

    /// Set the `created_after` query parameter.
    pub fn created_after(mut self, value: impl Into<chrono::DateTime<chrono::Utc>>) -> Self {
        self.created_after = Some(value.into());
        self
    }

    /// Set the `created_before` query parameter.
    pub fn created_before(mut self, value: impl Into<chrono::DateTime<chrono::Utc>>) -> Self {
        self.created_before = Some(value.into());
        self
    }

    /// Set the `created_by_user_ids` query parameter.
    pub fn created_by_user_ids(mut self, value: impl Into<Vec<String>>) -> Self {
        self.created_by_user_ids = Some(value.into());
        self
    }

    /// Set the `created_by_station_ids` query parameter.
    pub fn created_by_station_ids(mut self, value: impl Into<Vec<String>>) -> Self {
        self.created_by_station_ids = Some(value.into());
        self
    }

    /// Set the `exclude_units_with_parent` query parameter.
    pub fn exclude_units_with_parent(mut self, value: impl Into<bool>) -> Self {
        self.exclude_units_with_parent = Some(value.into());
        self
    }

    /// Set the `samples` query parameter.
    pub fn samples(mut self, value: impl Into<Vec<ListSample>>) -> Self {
        self.samples = Some(value.into());
        self
    }

    /// Set the `limit` query parameter.
    ///
    /// Maximum number of units to return.
    pub fn limit(mut self, value: impl Into<i64>) -> Self {
        self.limit = Some(value.into());
        self
    }

    /// Set the `cursor` query parameter.
    pub fn cursor(mut self, value: impl Into<i64>) -> Self {
        self.cursor = Some(value.into());
        self
    }

    /// Set the `sort_by` query parameter.
    ///
    /// Field to sort results by. last_run_at sorts by most recent test run date. last_run_procedure sorts by procedure name of the last run.
    pub fn sort_by(mut self, value: impl Into<UnitListSortBy>) -> Self {
        self.sort_by = Some(value.into());
        self
    }

    /// Set the `sort_order` query parameter.
    ///
    /// Sort order direction.
    pub fn sort_order(mut self, value: impl Into<ListSortOrder>) -> Self {
        self.sort_order = Some(value.into());
        self
    }

    /// Set the `metadata` query parameter.
    ///
    /// Filter units by custom metadata. Supports up to 5 keys per request. Per-key operators: string `{in: [...]}`/`{contains: "..."}`, number `{gte, lte, gt, lt, eq}`, bool `{eq: true|false}`.
    pub fn metadata(mut self, value: impl Into<serde_json::Value>) -> Self {
        self.metadata = Some(value.into());
        self
    }

    /// Set the `include_metadata` query parameter.
    ///
    /// When true, includes the unit metadata array in the response. Defaults to false to keep payloads small.
    pub fn include_metadata(mut self, value: impl Into<bool>) -> Self {
        self.include_metadata = Some(value.into());
        self
    }

    /// Override the server URL for this request.
    pub fn server_url(mut self, url: impl Into<String>) -> Self {
        self.server_url = Some(url.into());
        self
    }

    /// Override the timeout for this request.
    pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
        self.timeout = Some(timeout);
        self
    }

    /// Send the request.
    pub async fn send(self) -> Result<UnitListResponse> {

        let base_url = self.server_url
            .as_deref()
            .unwrap_or(&self.client.config.base_url);

        let url = format!("{}{}", base_url, "/v2/units");

        let mut request = self.client.http.request(
            reqwest::Method::GET,
            &url,
        );

        if let Some(timeout) = self.timeout {
            request = request.timeout(timeout);
        }

        if let Some(ref val) = self.search_query {
            request = request.query(&[("search_query", val.to_string())]);
        }
        if let Some(ref val) = self.ids {
            for item in val {
                request = request.query(&[("ids", item.to_string())]);
            }
        }
        if let Some(ref val) = self.serial_numbers {
            for item in val {
                request = request.query(&[("serial_numbers", item.to_string())]);
            }
        }
        if let Some(ref val) = self.part_numbers {
            for item in val {
                request = request.query(&[("part_numbers", item.to_string())]);
            }
        }
        if let Some(ref val) = self.revision_numbers {
            for item in val {
                request = request.query(&[("revision_numbers", item.to_string())]);
            }
        }
        if let Some(ref val) = self.batch_numbers {
            for item in val {
                request = request.query(&[("batch_numbers", item.to_string())]);
            }
        }
        if let Some(ref val) = self.procedure_ids {
            for item in val {
                request = request.query(&[("procedure_ids", item.to_string())]);
            }
        }
        if let Some(ref val) = self.outcomes {
            for item in val {
                request = request.query(&[("outcomes", item.to_string())]);
            }
        }
        if let Some(ref val) = self.started_after {
            request = request.query(&[("started_after", val.to_rfc3339_opts(chrono::SecondsFormat::Millis, true))]);
        }
        if let Some(ref val) = self.started_before {
            request = request.query(&[("started_before", val.to_rfc3339_opts(chrono::SecondsFormat::Millis, true))]);
        }
        if let Some(ref val) = self.latest_only {
            request = request.query(&[("latest_only", val.to_string())]);
        }
        if let Some(ref val) = self.run_count_min {
            request = request.query(&[("run_count_min", val.to_string())]);
        }
        if let Some(ref val) = self.run_count_max {
            request = request.query(&[("run_count_max", val.to_string())]);
        }
        if let Some(ref val) = self.created_after {
            request = request.query(&[("created_after", val.to_rfc3339_opts(chrono::SecondsFormat::Millis, true))]);
        }
        if let Some(ref val) = self.created_before {
            request = request.query(&[("created_before", val.to_rfc3339_opts(chrono::SecondsFormat::Millis, true))]);
        }
        if let Some(ref val) = self.created_by_user_ids {
            for item in val {
                request = request.query(&[("created_by_user_ids", item.to_string())]);
            }
        }
        if let Some(ref val) = self.created_by_station_ids {
            for item in val {
                request = request.query(&[("created_by_station_ids", item.to_string())]);
            }
        }
        if let Some(ref val) = self.exclude_units_with_parent {
            request = request.query(&[("exclude_units_with_parent", val.to_string())]);
        }
        if let Some(ref val) = self.samples {
            for item in val {
                request = request.query(&[("samples", item.to_string())]);
            }
        }
        if let Some(ref val) = self.limit {
            request = request.query(&[("limit", val.to_string())]);
        }
        if let Some(ref val) = self.cursor {
            request = request.query(&[("cursor", val.to_string())]);
        }
        if let Some(ref val) = self.sort_by {
            request = request.query(&[("sort_by", val.to_string())]);
        }
        if let Some(ref val) = self.sort_order {
            request = request.query(&[("sort_order", val.to_string())]);
        }
        if let Some(ref val) = self.metadata {
            request = request.query(&[("metadata", val.to_string())]);
        }
        if let Some(ref val) = self.include_metadata {
            request = request.query(&[("include_metadata", val.to_string())]);
        }


        let response = self.client.execute(
            request,
            "unit-list",
            base_url,
        ).await?;
        let result: UnitListResponse = response.json().await?;
        Ok(result)
    }
}

// ---------------------------------------------------------------------------
// CreateBuilder
// ---------------------------------------------------------------------------

/// Builder for [`UnitsClient::create`].
///
/// # Example
///
/// ```no_run
/// use tofupilot::TofuPilot;
///
/// # #[tokio::main]
/// # async fn main() -> tofupilot::Result<()> {
/// let client = TofuPilot::new("your-api-key");
/// let response = client.units().create()
///     .serial_number("value")
///     .part_number("value")
///     .revision_number("value")
///     .send()
///     .await?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug)]
pub struct CreateBuilder<'a> {
    client: &'a TofuPilot,
    serial_number: Option<String>,
    part_number: Option<String>,
    revision_number: Option<String>,
    sample: Option<NullableField<String>>,
    metadata: Option<std::collections::HashMap<String, serde_json::Value>>,
    server_url: Option<String>,
    timeout: Option<std::time::Duration>,
}

impl<'a> CreateBuilder<'a> {
    pub(crate) fn new(client: &'a TofuPilot) -> Self {
        Self {
            client,
            serial_number: None,
            part_number: None,
            revision_number: None,
            sample: None,
            metadata: None,
            server_url: None,
            timeout: None,
        }
    }

    /// Set the `serial_number` field.
    ///
    /// Unique serial number identifier for the unit. Must be unique within the organization.
    pub fn serial_number(mut self, value: impl Into<String>) -> Self {
        self.serial_number = Some(value.into());
        self
    }

    /// Set the `part_number` field.
    ///
    /// Component part number that defines what type of unit this is. If the part does not exist, it will be created.
    pub fn part_number(mut self, value: impl Into<String>) -> Self {
        self.part_number = Some(value.into());
        self
    }

    /// Set the `revision_number` field.
    ///
    /// Hardware revision identifier for the specific version of the part. If the revision does not exist, it will be created.
    pub fn revision_number(mut self, value: impl Into<String>) -> Self {
        self.revision_number = Some(value.into());
        self
    }

    /// Set the `sample` field.
    ///
    /// Reference-sample classification. 'golden' marks a known-good reference unit; 'failing' marks a known-faulty reference unit. Both are excluded from production analytics aggregates (FPY, Cpk, throughput) by default. Omit or null for regular production units.
    pub fn sample(mut self, value: impl Into<String>) -> Self {
        self.sample = Some(NullableField::Value(value.into()));
        self
    }

    /// Explicitly set `sample` to null.
    pub fn sample_null(mut self) -> Self {
        self.sample = Some(NullableField::Null);
        self
    }

    /// Set the `metadata` field.
    ///
    /// Custom metadata to attach to the unit (max 50 keys per unit). Plain object of key/value pairs; values can be string, number, or boolean. Type is detected from the value.
    pub fn metadata(mut self, value: impl Into<std::collections::HashMap<String, serde_json::Value>>) -> Self {
        self.metadata = Some(value.into());
        self
    }

    /// Set the full request body (alternative to setting individual fields).
    pub fn body(mut self, body: UnitCreateRequest) -> Self {
        self.serial_number = Some(body.serial_number);
        self.part_number = Some(body.part_number);
        self.revision_number = Some(body.revision_number);
        self.sample = Some(body.sample);
        if body.metadata.is_some() {
            self.metadata = body.metadata;
        }
        self
    }

    /// Override the server URL for this request.
    pub fn server_url(mut self, url: impl Into<String>) -> Self {
        self.server_url = Some(url.into());
        self
    }

    /// Override the timeout for this request.
    pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
        self.timeout = Some(timeout);
        self
    }

    /// Send the request.
    pub async fn send(self) -> Result<UnitCreateResponse> {

        let base_url = self.server_url
            .as_deref()
            .unwrap_or(&self.client.config.base_url);

        let url = format!("{}{}", base_url, "/v2/units");

        let mut request = self.client.http.request(
            reqwest::Method::POST,
            &url,
        );

        if let Some(timeout) = self.timeout {
            request = request.timeout(timeout);
        }


        let body = UnitCreateRequest {
            serial_number: self.serial_number
                .ok_or_else(|| Error::Validation(
                    "missing required field: serial_number".to_string(),
                ))?,
            part_number: self.part_number
                .ok_or_else(|| Error::Validation(
                    "missing required field: part_number".to_string(),
                ))?,
            revision_number: self.revision_number
                .ok_or_else(|| Error::Validation(
                    "missing required field: revision_number".to_string(),
                ))?,
            sample: self.sample.unwrap_or(NullableField::Absent),
            metadata: self.metadata,
        };
        request = request.json(&body);

        let response = self.client.execute(
            request,
            "unit-create",
            base_url,
        ).await?;
        let result: UnitCreateResponse = response.json().await?;
        Ok(result)
    }
}

// ---------------------------------------------------------------------------
// DeleteBuilder
// ---------------------------------------------------------------------------

/// Builder for [`UnitsClient::delete`].
///
/// # Example
///
/// ```no_run
/// use tofupilot::TofuPilot;
///
/// # #[tokio::main]
/// # async fn main() -> tofupilot::Result<()> {
/// let client = TofuPilot::new("your-api-key");
/// let response = client.units().delete()
///     .send()
///     .await?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug)]
pub struct DeleteBuilder<'a> {
    client: &'a TofuPilot,
    serial_numbers: Option<Vec<String>>,
    server_url: Option<String>,
    timeout: Option<std::time::Duration>,
}

impl<'a> DeleteBuilder<'a> {
    pub(crate) fn new(client: &'a TofuPilot) -> Self {
        Self {
            client,
            serial_numbers: None,
            server_url: None,
            timeout: None,
        }
    }

    /// Set the `serial_numbers` query parameter.
    ///
    /// Array of unit serial numbers to delete.
    pub fn serial_numbers(mut self, value: impl Into<Vec<String>>) -> Self {
        self.serial_numbers = Some(value.into());
        self
    }

    /// Override the server URL for this request.
    pub fn server_url(mut self, url: impl Into<String>) -> Self {
        self.server_url = Some(url.into());
        self
    }

    /// Override the timeout for this request.
    pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
        self.timeout = Some(timeout);
        self
    }

    /// Send the request.
    pub async fn send(self) -> Result<UnitDeleteResponse> {

        let base_url = self.server_url
            .as_deref()
            .unwrap_or(&self.client.config.base_url);

        let url = format!("{}{}", base_url, "/v2/units");

        let mut request = self.client.http.request(
            reqwest::Method::DELETE,
            &url,
        );

        if let Some(timeout) = self.timeout {
            request = request.timeout(timeout);
        }

        if let Some(ref val) = self.serial_numbers {
            for item in val {
                request = request.query(&[("serial_numbers", item.to_string())]);
            }
        }


        let response = self.client.execute(
            request,
            "unit-delete",
            base_url,
        ).await?;
        let result: UnitDeleteResponse = response.json().await?;
        Ok(result)
    }
}

// ---------------------------------------------------------------------------
// GetBuilder
// ---------------------------------------------------------------------------

/// Builder for [`UnitsClient::get`].
///
/// # Example
///
/// ```no_run
/// use tofupilot::TofuPilot;
///
/// # #[tokio::main]
/// # async fn main() -> tofupilot::Result<()> {
/// let client = TofuPilot::new("your-api-key");
/// let response = client.units().get()
///     .serial_number("value")
///     .send()
///     .await?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug)]
pub struct GetBuilder<'a> {
    client: &'a TofuPilot,
    serial_number: Option<String>,
    server_url: Option<String>,
    timeout: Option<std::time::Duration>,
}

impl<'a> GetBuilder<'a> {
    pub(crate) fn new(client: &'a TofuPilot) -> Self {
        Self {
            client,
            serial_number: None,
            server_url: None,
            timeout: None,
        }
    }

    /// Set the `serial_number` path parameter.
    ///
    /// Serial number of the unit to retrieve.
    pub fn serial_number(mut self, value: impl Into<String>) -> Self {
        self.serial_number = Some(value.into());
        self
    }

    /// Override the server URL for this request.
    pub fn server_url(mut self, url: impl Into<String>) -> Self {
        self.server_url = Some(url.into());
        self
    }

    /// Override the timeout for this request.
    pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
        self.timeout = Some(timeout);
        self
    }

    /// Send the request.
    pub async fn send(self) -> Result<UnitGetResponse> {
        let serial_number = self.serial_number
            .ok_or_else(|| Error::Validation(
                "missing required path parameter: serial_number".to_string(),
            ))?;
        let serial_number_encoded = utf8_percent_encode(&serial_number, URI_COMPONENT).to_string();

        let base_url = self.server_url
            .as_deref()
            .unwrap_or(&self.client.config.base_url);

        let url = format!(
            "{}/v2/units/{serial_number}",
            base_url,
            serial_number = serial_number_encoded,
        );

        let mut request = self.client.http.request(
            reqwest::Method::GET,
            &url,
        );

        if let Some(timeout) = self.timeout {
            request = request.timeout(timeout);
        }



        let response = self.client.execute(
            request,
            "unit-get",
            base_url,
        ).await?;
        let result: UnitGetResponse = response.json().await?;
        Ok(result)
    }
}

// ---------------------------------------------------------------------------
// UpdateBuilder
// ---------------------------------------------------------------------------

/// Builder for [`UnitsClient::update`].
///
/// # Example
///
/// ```no_run
/// use tofupilot::TofuPilot;
///
/// # #[tokio::main]
/// # async fn main() -> tofupilot::Result<()> {
/// let client = TofuPilot::new("your-api-key");
/// let response = client.units().update()
///     .send()
///     .await?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug)]
pub struct UpdateBuilder<'a> {
    client: &'a TofuPilot,
    serial_number: Option<String>,
    new_serial_number: Option<String>,
    part_number: Option<String>,
    revision_number: Option<String>,
    batch_number: Option<NullableField<String>>,
    attachments: Option<Vec<String>>,
    sample: Option<NullableField<String>>,
    metadata: Option<std::collections::HashMap<String, serde_json::Value>>,
    server_url: Option<String>,
    timeout: Option<std::time::Duration>,
}

impl<'a> UpdateBuilder<'a> {
    pub(crate) fn new(client: &'a TofuPilot) -> Self {
        Self {
            client,
            serial_number: None,
            new_serial_number: None,
            part_number: None,
            revision_number: None,
            batch_number: None,
            attachments: None,
            sample: None,
            metadata: None,
            server_url: None,
            timeout: None,
        }
    }

    /// Set the `serial_number` path parameter.
    ///
    /// Serial number of the unit to update.
    pub fn serial_number(mut self, value: impl Into<String>) -> Self {
        self.serial_number = Some(value.into());
        self
    }

    /// Set the `new_serial_number` field.
    ///
    /// New serial number for the unit.
    pub fn new_serial_number(mut self, value: impl Into<String>) -> Self {
        self.new_serial_number = Some(value.into());
        self
    }

    /// Set the `part_number` field.
    ///
    /// New part number for the unit.
    pub fn part_number(mut self, value: impl Into<String>) -> Self {
        self.part_number = Some(value.into());
        self
    }

    /// Set the `revision_number` field.
    ///
    /// New revision number for the unit.
    pub fn revision_number(mut self, value: impl Into<String>) -> Self {
        self.revision_number = Some(value.into());
        self
    }

    /// Set the `batch_number` field.
    ///
    /// New batch number for the unit. Set to null to remove batch.
    pub fn batch_number(mut self, value: impl Into<String>) -> Self {
        self.batch_number = Some(NullableField::Value(value.into()));
        self
    }

    /// Explicitly set `batch_number` to null.
    pub fn batch_number_null(mut self) -> Self {
        self.batch_number = Some(NullableField::Null);
        self
    }

    /// Set the `attachments` field.
    ///
    /// Array of upload IDs to attach to the unit.
    pub fn attachments(mut self, value: impl Into<Vec<String>>) -> Self {
        self.attachments = Some(value.into());
        self
    }

    /// Set the `sample` field.
    ///
    /// Reference-sample classification. 'golden' marks a known-good reference unit; 'failing' marks a known-faulty reference unit. Both are excluded from production analytics by default. Set to null to clear and treat as a production unit.
    pub fn sample(mut self, value: impl Into<String>) -> Self {
        self.sample = Some(NullableField::Value(value.into()));
        self
    }

    /// Explicitly set `sample` to null.
    pub fn sample_null(mut self) -> Self {
        self.sample = Some(NullableField::Null);
        self
    }

    /// Set the `metadata` field.
    ///
    /// Custom metadata to upsert on the unit. Plain object of key/value pairs. PATCH semantics: keys not present here are preserved. Pass `null` as a value to delete a key.
    pub fn metadata(mut self, value: impl Into<std::collections::HashMap<String, serde_json::Value>>) -> Self {
        self.metadata = Some(value.into());
        self
    }

    /// Set the full request body (alternative to setting individual fields).
    pub fn body(mut self, body: UnitUpdateRequestBody) -> Self {
        if body.new_serial_number.is_some() {
            self.new_serial_number = body.new_serial_number;
        }
        if body.part_number.is_some() {
            self.part_number = body.part_number;
        }
        if body.revision_number.is_some() {
            self.revision_number = body.revision_number;
        }
        self.batch_number = Some(body.batch_number);
        if body.attachments.is_some() {
            self.attachments = body.attachments;
        }
        self.sample = Some(body.sample);
        if body.metadata.is_some() {
            self.metadata = body.metadata;
        }
        self
    }

    /// Override the server URL for this request.
    pub fn server_url(mut self, url: impl Into<String>) -> Self {
        self.server_url = Some(url.into());
        self
    }

    /// Override the timeout for this request.
    pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
        self.timeout = Some(timeout);
        self
    }

    /// Send the request.
    pub async fn send(self) -> Result<UnitUpdateResponse> {
        let serial_number = self.serial_number
            .ok_or_else(|| Error::Validation(
                "missing required path parameter: serial_number".to_string(),
            ))?;
        let serial_number_encoded = utf8_percent_encode(&serial_number, URI_COMPONENT).to_string();

        let base_url = self.server_url
            .as_deref()
            .unwrap_or(&self.client.config.base_url);

        let url = format!(
            "{}/v2/units/{serial_number}",
            base_url,
            serial_number = serial_number_encoded,
        );

        let mut request = self.client.http.request(
            reqwest::Method::PATCH,
            &url,
        );

        if let Some(timeout) = self.timeout {
            request = request.timeout(timeout);
        }


        let body = UnitUpdateRequestBody {
            new_serial_number: self.new_serial_number,
            part_number: self.part_number,
            revision_number: self.revision_number,
            batch_number: self.batch_number.unwrap_or(NullableField::Absent),
            attachments: self.attachments,
            sample: self.sample.unwrap_or(NullableField::Absent),
            metadata: self.metadata,
        };
        request = request.json(&body);

        let response = self.client.execute(
            request,
            "unit-update",
            base_url,
        ).await?;
        let result: UnitUpdateResponse = response.json().await?;
        Ok(result)
    }
}

// ---------------------------------------------------------------------------
// AddChildBuilder
// ---------------------------------------------------------------------------

/// Builder for [`UnitsClient::add_child`].
///
/// # Example
///
/// ```no_run
/// use tofupilot::TofuPilot;
///
/// # #[tokio::main]
/// # async fn main() -> tofupilot::Result<()> {
/// let client = TofuPilot::new("your-api-key");
/// let response = client.units().add_child()
///     .child_serial_number("value")
///     .send()
///     .await?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug)]
pub struct AddChildBuilder<'a> {
    client: &'a TofuPilot,
    serial_number: Option<String>,
    child_serial_number: Option<String>,
    server_url: Option<String>,
    timeout: Option<std::time::Duration>,
}

impl<'a> AddChildBuilder<'a> {
    pub(crate) fn new(client: &'a TofuPilot) -> Self {
        Self {
            client,
            serial_number: None,
            child_serial_number: None,
            server_url: None,
            timeout: None,
        }
    }

    /// Set the `serial_number` path parameter.
    ///
    /// Serial number of the parent unit
    pub fn serial_number(mut self, value: impl Into<String>) -> Self {
        self.serial_number = Some(value.into());
        self
    }

    /// Set the `child_serial_number` field.
    ///
    /// Serial number of the sub-unit to add
    pub fn child_serial_number(mut self, value: impl Into<String>) -> Self {
        self.child_serial_number = Some(value.into());
        self
    }

    /// Set the full request body (alternative to setting individual fields).
    pub fn body(mut self, body: UnitAddChildRequestBody) -> Self {
        self.child_serial_number = Some(body.child_serial_number);
        self
    }

    /// Override the server URL for this request.
    pub fn server_url(mut self, url: impl Into<String>) -> Self {
        self.server_url = Some(url.into());
        self
    }

    /// Override the timeout for this request.
    pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
        self.timeout = Some(timeout);
        self
    }

    /// Send the request.
    pub async fn send(self) -> Result<UnitAddChildResponse> {
        let serial_number = self.serial_number
            .ok_or_else(|| Error::Validation(
                "missing required path parameter: serial_number".to_string(),
            ))?;
        let serial_number_encoded = utf8_percent_encode(&serial_number, URI_COMPONENT).to_string();

        let base_url = self.server_url
            .as_deref()
            .unwrap_or(&self.client.config.base_url);

        let url = format!(
            "{}/v2/units/{serial_number}/children",
            base_url,
            serial_number = serial_number_encoded,
        );

        let mut request = self.client.http.request(
            reqwest::Method::PUT,
            &url,
        );

        if let Some(timeout) = self.timeout {
            request = request.timeout(timeout);
        }


        let body = UnitAddChildRequestBody {
            child_serial_number: self.child_serial_number
                .ok_or_else(|| Error::Validation(
                    "missing required field: child_serial_number".to_string(),
                ))?,
        };
        request = request.json(&body);

        let response = self.client.execute(
            request,
            "unit-addChild",
            base_url,
        ).await?;
        let result: UnitAddChildResponse = response.json().await?;
        Ok(result)
    }
}

// ---------------------------------------------------------------------------
// RemoveChildBuilder
// ---------------------------------------------------------------------------

/// Builder for [`UnitsClient::remove_child`].
///
/// # Example
///
/// ```no_run
/// use tofupilot::TofuPilot;
///
/// # #[tokio::main]
/// # async fn main() -> tofupilot::Result<()> {
/// let client = TofuPilot::new("your-api-key");
/// let response = client.units().remove_child()
///     .serial_number("value")
///     .send()
///     .await?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug)]
pub struct RemoveChildBuilder<'a> {
    client: &'a TofuPilot,
    serial_number: Option<String>,
    child_serial_number: Option<String>,
    server_url: Option<String>,
    timeout: Option<std::time::Duration>,
}

impl<'a> RemoveChildBuilder<'a> {
    pub(crate) fn new(client: &'a TofuPilot) -> Self {
        Self {
            client,
            serial_number: None,
            child_serial_number: None,
            server_url: None,
            timeout: None,
        }
    }

    /// Set the `serial_number` path parameter.
    ///
    /// Serial number of the parent unit
    pub fn serial_number(mut self, value: impl Into<String>) -> Self {
        self.serial_number = Some(value.into());
        self
    }

    /// Set the `child_serial_number` query parameter.
    ///
    /// Serial number of the sub-unit to remove
    pub fn child_serial_number(mut self, value: impl Into<String>) -> Self {
        self.child_serial_number = Some(value.into());
        self
    }

    /// Override the server URL for this request.
    pub fn server_url(mut self, url: impl Into<String>) -> Self {
        self.server_url = Some(url.into());
        self
    }

    /// Override the timeout for this request.
    pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
        self.timeout = Some(timeout);
        self
    }

    /// Send the request.
    pub async fn send(self) -> Result<UnitRemoveChildResponse> {
        let serial_number = self.serial_number
            .ok_or_else(|| Error::Validation(
                "missing required path parameter: serial_number".to_string(),
            ))?;
        let serial_number_encoded = utf8_percent_encode(&serial_number, URI_COMPONENT).to_string();

        let base_url = self.server_url
            .as_deref()
            .unwrap_or(&self.client.config.base_url);

        let url = format!(
            "{}/v2/units/{serial_number}/children",
            base_url,
            serial_number = serial_number_encoded,
        );

        let mut request = self.client.http.request(
            reqwest::Method::DELETE,
            &url,
        );

        if let Some(timeout) = self.timeout {
            request = request.timeout(timeout);
        }

        if let Some(ref val) = self.child_serial_number {
            request = request.query(&[("child_serial_number", val.to_string())]);
        }


        let response = self.client.execute(
            request,
            "unit-removeChild",
            base_url,
        ).await?;
        let result: UnitRemoveChildResponse = response.json().await?;
        Ok(result)
    }
}

// ---------------------------------------------------------------------------
// CreateAttachmentBuilder
// ---------------------------------------------------------------------------

/// Builder for [`UnitsClient::create_attachment`].
///
/// # Example
///
/// ```no_run
/// use tofupilot::TofuPilot;
///
/// # #[tokio::main]
/// # async fn main() -> tofupilot::Result<()> {
/// let client = TofuPilot::new("your-api-key");
/// let response = client.units().create_attachment()
///     .name("value")
///     .send()
///     .await?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug)]
pub struct CreateAttachmentBuilder<'a> {
    client: &'a TofuPilot,
    serial_number: Option<String>,
    name: Option<String>,
    server_url: Option<String>,
    timeout: Option<std::time::Duration>,
}

impl<'a> CreateAttachmentBuilder<'a> {
    pub(crate) fn new(client: &'a TofuPilot) -> Self {
        Self {
            client,
            serial_number: None,
            name: None,
            server_url: None,
            timeout: None,
        }
    }

    /// Set the `serial_number` path parameter.
    ///
    /// Serial number of the unit to attach the file to. Matched case-insensitively.
    pub fn serial_number(mut self, value: impl Into<String>) -> Self {
        self.serial_number = Some(value.into());
        self
    }

    /// Set the `name` field.
    ///
    /// File name including extension (e.g. "calibration.pdf"). Used to determine content type and display name.
    pub fn name(mut self, value: impl Into<String>) -> Self {
        self.name = Some(value.into());
        self
    }

    /// Set the full request body (alternative to setting individual fields).
    pub fn body(mut self, body: UnitCreateAttachmentRequestBody) -> Self {
        self.name = Some(body.name);
        self
    }

    /// Override the server URL for this request.
    pub fn server_url(mut self, url: impl Into<String>) -> Self {
        self.server_url = Some(url.into());
        self
    }

    /// Override the timeout for this request.
    pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
        self.timeout = Some(timeout);
        self
    }

    /// Send the request.
    pub async fn send(self) -> Result<UnitCreateAttachmentResponse> {
        let serial_number = self.serial_number
            .ok_or_else(|| Error::Validation(
                "missing required path parameter: serial_number".to_string(),
            ))?;
        let serial_number_encoded = utf8_percent_encode(&serial_number, URI_COMPONENT).to_string();

        let base_url = self.server_url
            .as_deref()
            .unwrap_or(&self.client.config.base_url);

        let url = format!(
            "{}/v2/units/{serial_number}/attachments",
            base_url,
            serial_number = serial_number_encoded,
        );

        let mut request = self.client.http.request(
            reqwest::Method::POST,
            &url,
        );

        if let Some(timeout) = self.timeout {
            request = request.timeout(timeout);
        }


        let body = UnitCreateAttachmentRequestBody {
            name: self.name
                .ok_or_else(|| Error::Validation(
                    "missing required field: name".to_string(),
                ))?,
        };
        request = request.json(&body);

        let response = self.client.execute(
            request,
            "unit-createAttachment",
            base_url,
        ).await?;
        let result: UnitCreateAttachmentResponse = response.json().await?;
        Ok(result)
    }
}

// ---------------------------------------------------------------------------
// DeleteAttachmentBuilder
// ---------------------------------------------------------------------------

/// Builder for [`UnitsClient::delete_attachment`].
///
/// # Example
///
/// ```no_run
/// use tofupilot::TofuPilot;
///
/// # #[tokio::main]
/// # async fn main() -> tofupilot::Result<()> {
/// let client = TofuPilot::new("your-api-key");
/// let response = client.units().delete_attachment()
///     .serial_number("value")
///     .send()
///     .await?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug)]
pub struct DeleteAttachmentBuilder<'a> {
    client: &'a TofuPilot,
    serial_number: Option<String>,
    ids: Option<Vec<String>>,
    server_url: Option<String>,
    timeout: Option<std::time::Duration>,
}

impl<'a> DeleteAttachmentBuilder<'a> {
    pub(crate) fn new(client: &'a TofuPilot) -> Self {
        Self {
            client,
            serial_number: None,
            ids: None,
            server_url: None,
            timeout: None,
        }
    }

    /// Set the `serial_number` path parameter.
    ///
    /// Serial number of the unit. Matched case-insensitively.
    pub fn serial_number(mut self, value: impl Into<String>) -> Self {
        self.serial_number = Some(value.into());
        self
    }

    /// Set the `ids` query parameter.
    ///
    /// Attachment IDs to delete
    pub fn ids(mut self, value: impl Into<Vec<String>>) -> Self {
        self.ids = Some(value.into());
        self
    }

    /// Override the server URL for this request.
    pub fn server_url(mut self, url: impl Into<String>) -> Self {
        self.server_url = Some(url.into());
        self
    }

    /// Override the timeout for this request.
    pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
        self.timeout = Some(timeout);
        self
    }

    /// Send the request.
    pub async fn send(self) -> Result<UnitDeleteAttachmentResponse> {
        let serial_number = self.serial_number
            .ok_or_else(|| Error::Validation(
                "missing required path parameter: serial_number".to_string(),
            ))?;
        let serial_number_encoded = utf8_percent_encode(&serial_number, URI_COMPONENT).to_string();

        let base_url = self.server_url
            .as_deref()
            .unwrap_or(&self.client.config.base_url);

        let url = format!(
            "{}/v2/units/{serial_number}/attachments",
            base_url,
            serial_number = serial_number_encoded,
        );

        let mut request = self.client.http.request(
            reqwest::Method::DELETE,
            &url,
        );

        if let Some(timeout) = self.timeout {
            request = request.timeout(timeout);
        }

        if let Some(ref val) = self.ids {
            for item in val {
                request = request.query(&[("ids", item.to_string())]);
            }
        }


        let response = self.client.execute(
            request,
            "unit-deleteAttachment",
            base_url,
        ).await?;
        let result: UnitDeleteAttachmentResponse = response.json().await?;
        Ok(result)
    }
}

