use std::cmp::Reverse;
use std::ffi::OsStr;
use std::path::PathBuf;
use std::sync::Arc;

use anyhow::Result;
use assistant_tooling::{SavedToolFunctionCall, SavedUserAttachment};
use fs::Fs;
use futures::StreamExt;
use gpui::SharedString;
use regex::Regex;
use serde::{Deserialize, Serialize};
use util::paths::CONVERSATIONS_DIR;

use crate::MessageId;

#[derive(Serialize, Deserialize)]
pub struct SavedConversation {
    /// The schema version of the conversation.
    pub version: String,
    /// The title of the conversation, generated by the Assistant.
    pub title: String,
    pub messages: Vec<SavedChatMessage>,
}

#[derive(Serialize, Deserialize)]
pub enum SavedChatMessage {
    User {
        id: MessageId,
        body: String,
        attachments: Vec<SavedUserAttachment>,
    },
    Assistant {
        id: MessageId,
        messages: Vec<SavedAssistantMessagePart>,
        error: Option<SharedString>,
    },
}

#[derive(Serialize, Deserialize)]
pub struct SavedAssistantMessagePart {
    pub body: SharedString,
    pub tool_calls: Vec<SavedToolFunctionCall>,
}

pub struct SavedConversationMetadata {
    pub title: String,
    pub path: PathBuf,
    pub mtime: chrono::DateTime<chrono::Local>,
}

impl SavedConversationMetadata {
    pub async fn list(fs: Arc<dyn Fs>) -> Result<Vec<Self>> {
        fs.create_dir(&CONVERSATIONS_DIR).await?;

        let mut paths = fs.read_dir(&CONVERSATIONS_DIR).await?;
        let mut conversations = Vec::new();
        while let Some(path) = paths.next().await {
            let path = path?;
            if path.extension() != Some(OsStr::new("json")) {
                continue;
            }

            let pattern = r" - \d+.zed.\d.\d.\d.json$";
            let re = Regex::new(pattern).unwrap();

            let metadata = fs.metadata(&path).await?;
            if let Some((file_name, metadata)) = path
                .file_name()
                .and_then(|name| name.to_str())
                .zip(metadata)
            {
                // This is used to filter out conversations saved by the old assistant.
                if !re.is_match(file_name) {
                    continue;
                }

                let title = re.replace(file_name, "");
                conversations.push(Self {
                    title: title.into_owned(),
                    path,
                    mtime: metadata.mtime.into(),
                });
            }
        }
        conversations.sort_unstable_by_key(|conversation| Reverse(conversation.mtime));

        Ok(conversations)
    }
}
