error_tree!{

    pub enum ParseTokenDescriptionLineError {
        MissingToken,
        MissingDescription,
    }

    pub enum TokenizerError {
        TokenizerError(String),
    }

    pub enum GptBatchCreationError {
        OpenAIError(OpenAIError),
        IOError(std::io::Error),
        TokenizerError(TokenizerError),
        ParseTokenDescriptionLineError(ParseTokenDescriptionLineError),
        SerdeJsonError(serde_json::Error),
    }
}


/// Enumeration of API URLs.
#[derive(Debug, Serialize, Deserialize)]
pub enum GptApiUrl {

    #[serde(rename = "/v1/chat/completions")]
    ChatCompletions,
}

impl fmt::Display for GptApiUrl {

    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            GptApiUrl::ChatCompletions => write!(f, "/v1/chat/completions"),
        }
    }
}

/*!
  | basic example:
  | {
  |     "custom_id": "request-1", 
  |     "method": "POST", 
  |     "url": "/v1/chat/completions", 
  |     "body": {
  |         "model": "gpt-4", 
  |         "messages": [
  |             {"role": "system", "content": "You are a helpful assistant."},
  |             {"role": "user", "content": "Hello world!"}
  |         ],
  |         "max_tokens": 1000
  |     }
  | }
  */

/// Represents the complete request structure.
#[derive(Debug, Serialize, Deserialize)]
pub struct GptBatchAPIRequest {

    /// Identifier for the custom request.
    custom_id: String,

    /// HTTP method used for the request.
    #[serde(with = "http_method")]
    method: HttpMethod,

    /// URL of the API endpoint.
    #[serde(with = "api_url")]
    url:  GptApiUrl,

    /// Body of the request.
    body: GptRequestBody,
}

impl GptBatchAPIRequest {

    pub fn new_basic(idx: usize, system_message: &str, user_message: &str) -> Self {
        Self {
            custom_id: Self::custom_id_for_idx(idx),
            method:    HttpMethod::Post,
            url:       GptApiUrl::ChatCompletions,
            body:      GptRequestBody::new_basic(system_message,user_message),
        }
    }

    pub fn new_with_image(idx: usize, system_message: &str, user_message: &str, image_b64: &str) -> Self {
        Self {
            custom_id: Self::custom_id_for_idx(idx),
            method:    HttpMethod::Post,
            url:       GptApiUrl::ChatCompletions,
            body:      GptRequestBody::new_with_image(system_message,user_message,image_b64),
        }
    }
}

impl Display for GptBatchAPIRequest {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match serde_json::to_string(self) {
            Ok(json) => write!(f, "{}", json),
            Err(e) => {
                // Handle JSON serialization errors, though they shouldn't occur with proper struct definitions
                write!(f, "Error serializing to JSON: {}", e)
            }
        }
    }
}

impl GptBatchAPIRequest {

    pub(crate) fn custom_id_for_idx(idx: usize) -> String {
        format!("request-{}",idx)
    }
}

/// Custom serialization modules for enum string representations.
mod http_method {

    use super::*;

    pub fn serialize<S>(value: &HttpMethod, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&value.to_string())
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<HttpMethod, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s: String = Deserialize::deserialize(deserializer)?;
        match s.as_ref() {
            "POST" => Ok(HttpMethod::Post),
            _ => Err(serde::de::Error::custom("unknown method")),
        }
    }
}

mod api_url {

    use super::*;

    pub fn serialize<S>(value: &GptApiUrl, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&value.to_string())
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<GptApiUrl, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s: String = Deserialize::deserialize(deserializer)?;
        match s.as_ref() {
            "/v1/chat/completions" => Ok(GptApiUrl::ChatCompletions),
            _ => Err(serde::de::Error::custom("unknown URL")),
        }
    }
}

/// Individual message details in the request body.
#[derive(Debug, Serialize, Deserialize)]
pub struct GptMessage {
    /// Role of the participant (system/user).
    #[serde(with = "message_role")]
    role: GptMessageRole,
    /// Content of the message.
    content: ChatCompletionRequestUserMessageContent,
}

impl GptMessage {

    pub fn system_message(msg: &str) -> Self {
        Self {
            role:    GptMessageRole::System,
            content: ChatCompletionRequestUserMessageContent::Text(msg.to_string()),
        }
    }

    pub fn user_message(msg: &str) -> Self {
        Self {
            role:    GptMessageRole::User,
            content: ChatCompletionRequestUserMessageContent::Text(msg.to_string()),
        }
    }

    pub fn user_message_with_image(msg: &str, image_b64: &str) -> Self {

        Self {
            role:    GptMessageRole::User,
            content: ChatCompletionRequestUserMessageContent::Array(vec![
                ChatCompletionRequestMessageContentPart::Text(msg.into()),
                ChatCompletionRequestMessageContentPart::ImageUrl(ChatCompletionRequestMessageContentPartImage {
                    image_url: ImageUrl {
                        url:    image_b64.to_string(),
                        detail: Some(ImageDetail::High),
                    }
                }),
            ]),
        }
    }
}

/// Enumeration of roles in a message.
#[derive(Debug, Serialize, Deserialize)]
pub enum GptMessageRole {
    System,
    User,
}

pub(crate) mod message_role {

    use super::*;

    /// Serialize the `GptMessageRole` enum into a string.
    pub fn serialize<S>(value: &GptMessageRole, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let role_str = match value {
            GptMessageRole::System => "system",
            GptMessageRole::User => "user",
        };
        serializer.serialize_str(role_str)
    }

    /// Deserialize a string into a `GptMessageRole` enum.
    pub fn deserialize<'de, D>(deserializer: D) -> Result<GptMessageRole, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s: String = Deserialize::deserialize(deserializer)?;
        match s.as_ref() {
            "system" => Ok(GptMessageRole::System),
            "user" => Ok(GptMessageRole::User),
            _ => Err(DeError::custom("unknown message role")),
        }
    }
}



/// Supported model types.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum GptModelType {
    Gpt4o,
    Gpt4Turbo,
}

impl fmt::Display for GptModelType {

    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            GptModelType::Gpt4o     => write!(f, "gpt-4o"),
            GptModelType::Gpt4Turbo => write!(f, "gpt-4-turbo-2024-04-09"),
        }
    }
}

pub(crate) mod model_type {

    use super::*;

    pub fn serialize<S>(value: &GptModelType, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&value.to_string())
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<GptModelType, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s: String = Deserialize::deserialize(deserializer)?;
        match s.as_ref() {
            "gpt-4o" => Ok(GptModelType::Gpt4o),
            "gpt-4" => Ok(GptModelType::Gpt4Turbo),
            _ => Err(serde::de::Error::custom("unknown model type")),
        }
    }
}

/// Body details of the API request.
#[derive(Debug, Serialize, Deserialize)]
pub struct GptRequestBody {

    /// Model used for the request.
    #[serde(with = "model_type")]
    model: GptModelType,

    /// Array of messages exchanged in the request.
    messages: Vec<GptMessage>,

    /// Maximum number of tokens to be used by the model.
    max_tokens: u32,
}

impl GptRequestBody {

    pub fn default_max_tokens() -> u32 {
        1024 
    }

    pub fn default_max_tokens_given_image(_image_b64: &str) -> u32 {
        //TODO: is this the right value?
        2048
    }

    pub fn new_basic(system_message: &str, user_message: &str) -> Self {
        Self {
            model: GptModelType::Gpt4Turbo,
            messages: vec![
                GptMessage::system_message(system_message),
                GptMessage::user_message(user_message),
            ],
            max_tokens: Self::default_max_tokens(),
        }
    }

    pub fn new_with_image(system_message: &str, user_message: &str, image_b64: &str) -> Self {
        Self {
            model: GptModelType::Gpt4o,
            messages: vec![
                GptMessage::system_message(system_message),
                GptMessage::user_message_with_image(user_message,image_b64),
            ],
            max_tokens: Self::default_max_tokens_given_image(image_b64),
        }
    }
}


/// Enumeration of possible HTTP methods.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum HttpMethod {
    Get,
    Post,
}

impl fmt::Display for HttpMethod {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            HttpMethod::Get => write!(f, "GET"),
            HttpMethod::Post => write!(f, "POST"),
        }
    }
}
