use std::error::Error;

#[cfg(not(target_arch = "wasm32"))]
use super::BoxedChatModel;
#[cfg(not(target_arch = "wasm32"))]
use super::BoxedStructuredChatModel;
use super::Chat;
use super::ChatModel;
use super::ChatSession;
use super::CreateChatSession;
use super::CreateDefaultChatConstraintsForType;
use super::StructuredChatModel;
use super::Task;

/// An extension trait for chat models with helpers for handling chat sessions. This trait is implemented automatically for all [`crate::ChatModel`]s.
pub trait ChatModelExt: CreateChatSession {
    /// Create a new chat session with the model.
    #[doc = include_str!("../../docs/chat.md")]
    fn chat(&self) -> Chat<Self>
    where
        Self: Clone,
    {
        Chat::new(self.clone())
    }

    /// Create a new task with the model.
    #[doc = include_str!("../../docs/task.md")]
    fn task(&self, description: impl ToString) -> Task<Self>
    where
        Self: Clone,
    {
        Task::new(self.clone(), description)
    }

    /// Erase the type of the chat model. This can be used to make multiple implementations of
    /// [`ChatModel`] compatible with the same type.
    ///
    /// # Example
    ///
    /// ```rust, no_run
    /// # #![allow(unused)]
    /// # use kalosm::language::*;
    /// #
    /// # #[tokio::main]
    /// # async fn main() {
    /// let model = loop {
    ///     let input = prompt_input("Choose Model (gpt, claude, llama, or phi): ").unwrap();
    ///     match input.to_lowercase().as_str() {
    ///         "gpt" => {
    ///             break OpenAICompatibleChatModel::builder()
    ///                 .with_gpt_4o_mini()
    ///                 .build()
    ///                 .boxed_chat_model()
    ///         }
    ///         "claude" => {
    ///             break AnthropicCompatibleChatModel::builder()
    ///                 .with_claude_3_5_haiku()
    ///                 .build()
    ///                 .boxed_chat_model()
    ///         }
    ///         "llama" => {
    ///             break Llama::builder()
    ///                 .with_source(LlamaSource::llama_3_1_8b_chat())
    ///                 .build()
    ///                 .await
    ///                 .unwrap()
    ///                 .boxed_chat_model()
    ///         }
    ///         "phi" => {
    ///             break Llama::builder()
    ///                 .with_source(LlamaSource::phi_3_5_mini_4k_instruct())
    ///                 .build()
    ///                 .await
    ///                 .unwrap()
    ///                 .boxed_chat_model()
    ///         }
    ///         _ => {}
    ///     }
    /// };
    ///
    /// let mut chat = model
    ///     .chat()
    ///     .with_system_prompt("The assistant will act like a pirate");
    ///
    /// // Then chat with the session
    /// loop {
    ///     chat(&prompt_input("\n> ").unwrap())
    ///         .to_std_out()
    ///         .await
    ///         .unwrap();
    /// }
    /// # }
    /// ```
    #[cfg(not(target_arch = "wasm32"))]
    fn boxed_chat_model(self) -> BoxedChatModel
    where
        Self: ChatModel<
                Error: Send + Sync + std::error::Error + 'static,
                ChatSession: ChatSession<Error: std::error::Error + Send + Sync + 'static>
                                 + Clone
                                 + Send
                                 + Sync
                                 + 'static,
            > + Sized
            + Send
            + Sync
            + 'static,
    {
        BoxedChatModel::new(self)
    }

    /// Erase the type of the structured chat model. This can be used to make multiple implementations of
    /// [`StructuredChatModel`] compatible with the same type.
    ///
    /// # Example
    ///
    /// ```rust, no_run
    /// # #![allow(unused)]
    /// # use kalosm::language::*;
    /// # use serde::Deserialize;
    /// # #[tokio::main]
    /// # async fn main() {
    /// // You can derive an efficient parser for your struct with the `Parse` trait
    /// // OpenAI doesn't support root anyof schemas, so we need to wrap the constraints in a struct
    /// #[derive(Parse, Clone, Schema, Deserialize, Debug)]
    /// struct Response {
    ///     action: Action,
    /// }
    ///
    /// #[derive(Parse, Clone, Schema, Deserialize, Debug)]
    /// #[serde(tag = "type")]
    /// #[serde(content = "data")]
    /// pub enum Action {
    ///     Do(String),
    ///     Say(String),
    /// }
    ///
    /// let model: BoxedStructuredChatModel<Response> = loop {
    ///     let input = prompt_input("Choose Model (gpt, llama, or phi): ").unwrap();
    ///     match input.to_lowercase().as_str() {
    ///         "gpt" => {
    ///             break OpenAICompatibleChatModel::builder()
    ///                 .with_gpt_4o_mini()
    ///                 .build()
    ///                 .boxed_typed_chat_model()
    ///         }
    ///         "llama" => {
    ///             break Llama::builder()
    ///                 .with_source(LlamaSource::llama_3_1_8b_chat())
    ///                 .build()
    ///                 .await
    ///                 .unwrap()
    ///                 .boxed_typed_chat_model()
    ///         }
    ///         "phi" => {
    ///             break Llama::builder()
    ///                 .with_source(LlamaSource::phi_3_5_mini_4k_instruct())
    ///                 .build()
    ///                 .await
    ///                 .unwrap()
    ///                 .boxed_typed_chat_model()
    ///         }
    ///         _ => {}
    ///     }
    /// };
    ///
    /// let mut chat = model
    ///     .chat()
    ///     .with_system_prompt("The assistant will act like a pirate. You will respond with either something you do or something you say. Respond with JSON in the format { \"type\": \"Say\", \"data\": \"hello\" } or { \"type\": \"Do\", \"data\": \"run away\" }");
    ///
    /// // Then chat with the session
    /// loop {
    ///     let mut response = chat(&prompt_input("\n> ").unwrap()).typed::<Response>();
    ///     response.to_std_out().await.unwrap();
    ///     println!("{:?}", response.await);
    /// }
    /// # }
    /// ```
    #[cfg(not(target_arch = "wasm32"))]
    fn boxed_typed_chat_model<T>(self) -> BoxedStructuredChatModel<T>
    where
        Self: StructuredChatModel<
                Self::DefaultConstraints,
                Error: Send + Sync + Error + 'static,
                ChatSession: ChatSession<Error: Error + Send + Sync + 'static>
                                 + Clone
                                 + Send
                                 + Sync
                                 + 'static,
            > + CreateDefaultChatConstraintsForType<T>
            + Sized
            + Send
            + Sync
            + 'static,
        T: 'static,
    {
        BoxedStructuredChatModel::new(self)
    }
}

impl<M: CreateChatSession> ChatModelExt for M {}