//! API types for ComfyUI SDK

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Queue status response
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueueStatus {
    pub queue_running: Vec<serde_json::Value>,
    pub queue_pending: Vec<serde_json::Value>,
}

/// Queue item (parsed from array format)
#[derive(Debug, Clone)]
pub struct QueueItem {
    pub prompt_id: String,
    pub number: u32,
    pub outputs: HashMap<String, serde_json::Value>,
}

impl QueueStatus {
    /// Parse running queue items from raw JSON values
    pub fn get_running_items(&self) -> Vec<QueueItem> {
        self.queue_running.iter()
            .filter_map(|item| Self::parse_queue_item(item))
            .collect()
    }

    /// Parse pending queue items from raw JSON values
    pub fn get_pending_items(&self) -> Vec<QueueItem> {
        self.queue_pending.iter()
            .filter_map(|item| Self::parse_queue_item(item))
            .collect()
    }

    /// Parse a single queue item from JSON value
    fn parse_queue_item(value: &serde_json::Value) -> Option<QueueItem> {
        if let serde_json::Value::Array(arr) = value {
            if arr.len() >= 2 {
                let number = match &arr[0] {
                    serde_json::Value::Number(n) => n.as_u64()? as u32,
                    _ => return None,
                };

                let prompt_id = match &arr[1] {
                    serde_json::Value::String(s) => s.clone(),
                    serde_json::Value::Number(n) => n.to_string(),
                    _ => return None,
                };

                return Some(QueueItem {
                    prompt_id,
                    number,
                    outputs: HashMap::new(), // ComfyUI queue format doesn't include outputs
                });
            }
        }
        None
    }
}

/// Prompt request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptRequest {
    pub prompt: HashMap<String, serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub client_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extra_data: Option<HashMap<String, serde_json::Value>>,
}

/// Prompt response
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptResponse {
    pub prompt_id: String,
    pub number: u32,
    pub node_errors: HashMap<String, serde_json::Value>,
}

/// History item
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryItem {
    pub prompt: Vec<serde_json::Value>,
    pub outputs: HashMap<String, HashMap<String, Vec<HistoryOutput>>>,
    pub status: HistoryStatus,
}

/// History output
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryOutput {
    pub filename: String,
    pub subfolder: String,
    #[serde(rename = "type")]
    pub output_type: String,
}

/// History status
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryStatus {
    pub status_str: String,
    pub completed: bool,
    pub messages: Vec<Vec<serde_json::Value>>,
}

/// System stats
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemStats {
    pub system: SystemInfo,
    pub devices: Vec<DeviceInfo>,
}

/// System information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemInfo {
    pub os: String,
    pub python_version: String,
    pub embedded_python: bool,
}

/// Device information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceInfo {
    pub name: String,
    #[serde(default = "default_device_type")]
    pub device_type: String,
    pub vram_total: u64,
    pub vram_free: u64,
    pub torch_vram_total: u64,
    pub torch_vram_free: u64,
}

/// Default device type when not provided by ComfyUI
fn default_device_type() -> String {
    "unknown".to_string()
}

/// Object info response
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObjectInfo {
    #[serde(flatten)]
    pub nodes: HashMap<String, NodeInfo>,
}

/// Node information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeInfo {
    pub input: NodeInputInfo,
    pub output: Vec<String>,
    pub output_is_list: Vec<bool>,
    pub output_name: Vec<String>,
    pub name: String,
    pub display_name: String,
    pub description: String,
    pub category: String,
    pub python_module: String,
}

/// Node input information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeInputInfo {
    pub required: HashMap<String, Vec<serde_json::Value>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub optional: Option<HashMap<String, Vec<serde_json::Value>>>,
}

/// Upload image response
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UploadImageResponse {
    pub name: String,
    pub subfolder: String,
    #[serde(rename = "type")]
    pub image_type: String,
}

/// View metadata response
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ViewMetadata {
    #[serde(flatten)]
    pub metadata: HashMap<String, serde_json::Value>,
}

/// Custom deserializer that can handle both string and number types
#[allow(dead_code)]
fn deserialize_string_or_number<'de, D>(deserializer: D) -> Result<String, D::Error>
where
    D: serde::Deserializer<'de>,
{
    use serde::de::{self, Visitor};
    use std::fmt;

    struct StringOrNumberVisitor;

    impl<'de> Visitor<'de> for StringOrNumberVisitor {
        type Value = String;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("a string or number")
        }

        fn visit_str<E>(self, value: &str) -> Result<String, E>
        where
            E: de::Error,
        {
            Ok(value.to_string())
        }

        fn visit_string<E>(self, value: String) -> Result<String, E>
        where
            E: de::Error,
        {
            Ok(value)
        }

        fn visit_i64<E>(self, value: i64) -> Result<String, E>
        where
            E: de::Error,
        {
            Ok(value.to_string())
        }

        fn visit_u64<E>(self, value: u64) -> Result<String, E>
        where
            E: de::Error,
        {
            Ok(value.to_string())
        }

        fn visit_f64<E>(self, value: f64) -> Result<String, E>
        where
            E: de::Error,
        {
            Ok(value.to_string())
        }
    }

    deserializer.deserialize_any(StringOrNumberVisitor)
}
