use std::collections::HashMap;
//use std::io::Read;
use std::path::Path;
//use std::process::{Command, Stdio};

use chrono::Local;
use openai_dive::v1::{
    api::Client,
    resources::chat::{
        ChatCompletionFunction,
        ChatCompletionParametersBuilder,
        ChatCompletionTool,
        ChatCompletionToolType,
        ChatMessage,
        ChatMessageContent,
    },
};
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use tokio::{
    sync::mpsc::Sender,
    time::{
        sleep,
        Duration,
    },
    //process::Command,
};
use tracing::{event, Level};

use crate::{
    info::{
        insert_message,
        get_messages,
        get_messages_num,
        DataType,
        update_approval,
        approved,
        get_tool_calling_count,
    },
    openai::{
        for_tool::{
            call_tool_not_use_stream,
            CallToolResult,
        },
        for_chat::not_use_stream,
    },
    parse_paras::PARAS,
    error::MyError,
    api::handlers::chat::{
        MainData,
        MetaData,
    },
    skills::SelectedSkills,
};

pub mod built_in_tools;
pub mod external_tools;

use built_in_tools::{BuiltInTools, Group, filesystem::edit_file::Params};
use external_tools::ExternalTools;

/// html pulldown option selected tools
pub enum SelectedTools {
    All,               // all tools
    AllBuiltIn,        // all built-in tools
    AllExternal,       // all external tools
    Group(String),     // built-in group, start with `built_in_group_`, select all tools of one group
    Single(String),    // single built-in or external tool id
    AllMcp,            // all mcp tools
    McpServer(String), // single mcp server id, start with `mcp_server_`, select all tools of one server
    McpTool(String),   // single mcp tool `name__id`, select by tool name and server id
}

/// trait for built-in tools & external tools
pub trait MyTools {
    /// run tool
    fn run(&self, id: &str, args: &str) -> Result<(String, Option<String>), MyError>;

    /// get all selected tools name (name format: `name__id`, max name length is 26), description and schema
    fn get_desc_and_schema(&self, selected_tools: Vec<String>) -> Vec<(String, String, Value)>;

    /// select all tools, return uuid vector
    fn select_all_tools(&self) -> Vec<String>;

    /// get approval message
    fn get_approval(&self, id: &str, args: &str, info: Option<String>, is_en: bool) -> Result<Option<String>, MyError>;
}

/// all tools: built-in tools & external tools
pub struct Tools {
    built_in: BuiltInTools,
    external: ExternalTools,
    pub html: String, // html pulldown options
}

impl Tools {
    /// create new Tools, ExternalTools from config file
    pub fn new(external: ExternalTools, english: bool) -> Result<Self, MyError> {
        let built_in = BuiltInTools::new()?;
        let mut options: Vec<String> = Vec::with_capacity(3 + built_in.id_map.len() + built_in.groups.len() * 2 + 2 + external.id_map.len());
        // built-in pulldown options
        let mut groups: Vec<(Group, String)> = built_in.groups.iter().map(|g| (g.clone(), g.to_string())).collect();
        groups.sort_by(|a, b| a.1.cmp(&b.1)); // sort by group name
        if english {
            options.push("<option value='not_select_any_tools' selected>⚪ not using any tools</option>".to_string());
            options.push("                <option value='select_all_tools'>🔴 select all tools</option>".to_string());
            options.push("                <optgroup label='built-in tools'>".to_string());
            options.push("                    <option value='select_all_built_in'>🟢 select all built-in tools</option>".to_string());
        } else {
            options.push("<option value='not_select_any_tools' selected>⚪ 不使用任何工具</option>".to_string());
            options.push("                <option value='select_all_tools'>🔴 选择所有工具</option>".to_string());
            options.push("                <optgroup label='内置工具'>".to_string());
            options.push("                    <option value='select_all_built_in'>🟢 选择所有内置工具</option>".to_string());
        }
        for g in groups {
            let mut tools: Vec<(String, String, String)> = built_in.id_map.iter().filter(|(_, v)| v.group == g.0).map(|(k, v)| (k.clone(), v.tool.name(), v.tool.description())).collect();
            tools.sort_by(|a, b| a.1.cmp(&b.1)); // sort by tool name
            options.push(format!("                    <option disabled>--{}--</option>", g.1));
            if english {
                options.push(format!("                    <option value='built_in_group_{}'>🟢 select all {}</option>", g.1, g.1));
            } else {
                options.push(format!("                    <option value='built_in_group_{}'>🟢 选择所有{}</option>", g.1, g.1));
            }
            for t in tools {
                options.push(format!("                    <option value='{}' title=\"{}\">{}</option>", t.0, t.2.replace("\"", "&quot;"), t.1));
            }
        }
        options.push("                </optgroup>".to_string());
        // external pulldown options
        if !external.id_map.is_empty() {
            if english {
                options.push("                <optgroup label='external tools'>".to_string());
                options.push("                    <option value='select_all_external'>🟣 select all external tools</option>".to_string());
            } else {
                options.push("                <optgroup label='外部工具'>".to_string());
                options.push("                    <option value='select_all_external'>🟣 选择所有外部工具</option>".to_string());
            }
            for (k, v) in &external.id_map {
                options.push(format!("                    <option value='{}' title=\"{}\">{}</option>", k, v.description.replace("\"", "&quot;"), v.name));
            }
            options.push("                </optgroup>".to_string());
        }
        // return
        Ok(Self {built_in, external, html: options.join("\n")})
    }

    /// run tool
    pub fn run(&self, id: &str, args: &str) -> Result<(String, Option<String>), MyError> {
        if self.built_in.id_map.contains_key(id) {
            self.built_in.run(id, args)
        } else if self.external.id_map.contains_key(id) {
            self.external.run(id, args)
        } else {
            Err(MyError::ToolNotExistError{id: id.to_string(), info: "Tools::run()".to_string()})
        }
    }

    /// get all selected tools for LLM api function calling
    /// tool name format: `name__id`, max name length is 26
    pub fn get_desc_and_schema(&self, selected_tools: &Option<SelectedTools>) -> Result<Vec<ChatCompletionTool>, MyError> {
        // get selected tools
        let (selected_builtin_tools, selected_external_tools) = match selected_tools {
            Some(s) => match s {
                SelectedTools::All => (self.built_in.select_all_tools(), self.external.select_all_tools()), // all tools
                SelectedTools::AllBuiltIn => (self.built_in.select_all_tools(), Vec::new()), // all built-in tools
                SelectedTools::AllExternal => (Vec::new(), self.external.select_all_tools()), // all external tools
                SelectedTools::Group(group) => (self.built_in.select_tools_by_group(&group)?, Vec::new()), // built-in group
                SelectedTools::Single(id) => { // single built-in or external tool id
                    if self.built_in.id_map.contains_key(id) {
                        (vec![id.to_string()], Vec::new())
                    } else if self.external.id_map.contains_key(id) {
                        (Vec::new(), vec![id.to_string()])
                    } else {
                        return Err(MyError::ToolNotExistError{id: id.to_string(), info: "Tools::select_tools()".to_string()})
                    }
                },
                SelectedTools::AllMcp => (Vec::new(), Vec::new()), // all mcp tools
                SelectedTools::McpServer(_) => (Vec::new(), Vec::new()), // single mcp server id, start with `mcp_server_`, select all tools of one server
                SelectedTools::McpTool(_) => (Vec::new(), Vec::new()), // single mcp tool `name__id`, select by tool name and server id
            },
            None => (Vec::new(), Vec::new()), // not select any tool
        };
        // get name, description and schema
        let mut tools: Vec<ChatCompletionTool> = Vec::new();
        let desc_and_schema_builtin = self.built_in.get_desc_and_schema(selected_builtin_tools);
        let desc_and_schema_external = self.external.get_desc_and_schema(selected_external_tools);
        for tool in desc_and_schema_builtin.into_iter().chain(desc_and_schema_external) {
            tools.push(
                ChatCompletionTool {
                    r#type: ChatCompletionToolType::Function,
                    function: ChatCompletionFunction {
                        name: tool.0,
                        description: Some(tool.1),
                        parameters: tool.2,
                    },
                }
            );
        }
        Ok(tools)
    }

    /// check contain tool id
    pub fn contain_tool_id(&self, id: &str) -> bool {
        if self.built_in.id_map.contains_key(id) {
            true
        } else if self.external.id_map.contains_key(id) {
            true
        } else {
            false
        }
    }

    /// get approval message
    pub fn get_approval(&self, id: &str, args: &str, info: Option<String>, is_en: bool) -> Result<Option<String>, MyError> {
        if self.built_in.id_map.contains_key(id) {
            self.built_in.get_approval(id, args, info, is_en)
        } else if self.external.id_map.contains_key(id) {
            self.external.get_approval(id, args, info, is_en)
        } else {
            Err(MyError::ToolNotExistError{id: id.to_string(), info: "Tools::get_approval()".to_string()})
        }
    }
}

/// chat with tools: built-in + external tools
/// This just a simple loop, if not call tool, break the loop, return result
/// +-------------------------------------------------------------+
/// |      user query                                             |
/// |          |                                                  |
/// | +--------+------------+                                     |
/// | |        |            |                                     |
/// | |        V            |                                     |
/// | | context messages <--+--------------------+                |
/// | |        |            |                    |                |
/// | |        V            |                    | add            |
/// | |       LLM           |                    | each           |
/// | |        |            |                    | call           |
/// | |        V            |                    | tool           |
/// | |    call tool ?      |                    | result         |
/// | |        |            |                    | to             |
/// | |    +-------+        |                    | context        |
/// | |    |       |        |                    |                |
/// | |    V       V        |    +------------------------------+ |
/// | |   No      Yes ------+--> | run MCP tools, return result | |
/// | |    |                |    +------------------------------+ |
/// | +----+----------------+                                     |
/// |      |                                                      |
/// |      V                                                      |
/// |   result                                                    |
/// +-------------------------------------------------------------+
/// parameters:
/// +--------------------------------------------------------------------+
/// | selected_tools: html pulldown selected tools                       |
/// | uuid:           current chat uuid                                  |
/// | sender:         channel for send final result to user page         |
/// | client:         OpenAI api Client for send request                 |
/// | para_builder:   update history messages before send request to LLM |
/// | model:          model name                                         |
/// +--------------------------------------------------------------------+
/// function calling:
/// https://api-docs.deepseek.com/zh-cn/guides/function_calling
/// https://docs.bigmodel.cn/cn/guide/capabilities/function-calling
/// https://platform.moonshot.cn/docs/api/tool-use
pub async fn run_tools(selected_tools: Option<SelectedTools>, selected_skills: Option<SelectedSkills>, uuid: String, sender: Sender<Vec<u8>>, client: Client, mut para_builder: ChatCompletionParametersBuilder, model: &str) -> Result<(), MyError> {
    // get built-in and external tools schame
    let mut tool_schema = PARAS.tools.get_desc_and_schema(&selected_tools)?;
    // get mcp tools schema
    let mcp_schema = PARAS.mcp_servers.get_desc_and_schema(&selected_tools).await?;
    // 如果指定了skills，则加入一个激活指定skill的tool
    if selected_skills.is_some() {
        tool_schema.push(
            ChatCompletionTool {
                r#type: ChatCompletionToolType::Function,
                function: ChatCompletionFunction {
                    name: "activate_skill".to_string(),
                    description: Some("Activate an agent skill to load its full instructions. IMPORTANT: You must CALL this tool (not write it as text) to activate a skill. Use this when you see a relevant skill in the available skills list and need its detailed instructions to complete a task.".to_string()),
                    parameters: json!({
                        "properties": {
                            "skill_name": {
                                "type": "string",
                                "description": "The name of the skill to activate.",
                            },
                        },
                        "required": ["skill_name"],
                        "type": "object",
                    }),
                },
            }
        );
        // 如果没有选择`run_command`，则自动添加这个工具
        if tool_schema.iter().any(|t| !t.function.name.starts_with("run_command")) {
            let run_command_schema: Vec<_> = PARAS
                .tools
                .built_in
                .id_map
                .iter()
                .filter(|(_, v)| v.tool.name() == "run_command")
                .map(|(k, v)| (format!("{}__{}", v.tool.name(), k), v.tool.description(), v.tool.schema()))
                .collect();
            tool_schema.push(
                ChatCompletionTool {
                    r#type: ChatCompletionToolType::Function,
                    function: ChatCompletionFunction {
                        name: run_command_schema[0].0.clone(),
                        description: Some(run_command_schema[0].1.clone()),
                        parameters: run_command_schema[0].2.clone(),
                    },
                }
            );
        }
        // 如果没有选择`run_script`，则自动添加这个工具
        if tool_schema.iter().any(|t| !t.function.name.starts_with("run_script")) {
            let run_command_schema: Vec<_> = PARAS
                .tools
                .built_in
                .id_map
                .iter()
                .filter(|(_, v)| v.tool.name() == "run_script")
                .map(|(k, v)| (format!("{}__{}", v.tool.name(), k), v.tool.description(), v.tool.schema()))
                .collect();
            tool_schema.push(
                ChatCompletionTool {
                    r#type: ChatCompletionToolType::Function,
                    function: ChatCompletionFunction {
                        name: run_command_schema[0].0.clone(),
                        description: Some(run_command_schema[0].1.clone()),
                        parameters: run_command_schema[0].2.clone(),
                    },
                }
            );
        }
    }
    // prepare buider for call tool
    tool_schema.extend(mcp_schema);
    // tool名称加了`_uuid第一部分`保证不重复，但是上下文很长时可能导致模型返回的要调用的模型丢失了`_uuid第一部分`，导致调用模型失败
    // 这里把不含`_uuid第一部分`的tool名称作为key，将对应的完整tool名称作为value，存入HashMap，当`_uuid第一部分`丢失，也能确认具体要调用的模型
    let tool_map = get_tool_hashmap(&tool_schema);
    para_builder.tools(tool_schema);
    let mut history_messages: Vec<ChatMessage> = get_messages(&uuid); // store all messages as context log, this is temp history, will not add to the user's main history
    if let Some(sele_skills) = selected_skills {
        let skill_prompt = match sele_skills {
            SelectedSkills::All => PARAS.skills.get_all_available_skills_prompt(),
            SelectedSkills::Group(group) => PARAS.skills.get_group_available_skills_prompt(group),
            SelectedSkills::Single(idx) => PARAS.skills.get_single_available_skill_prompt(idx),
        };
        history_messages.push(
            ChatMessage::User{
                content: ChatMessageContent::Text(skill_prompt),
                name: None,
            }
        );
    }
    let mut call_tool_count = get_tool_calling_count(&uuid); // call tool count
    let call_tool_limit = call_tool_count+100; // 调用工具的次数限制
    let mut try_count = 0;
    let mut is_first: bool;
    let mut real_name: String; // 要调用的工具的真实名称，含有`_uuid第一部分`后缀

    'outer: loop {
        // send query to LLM
        para_builder.messages(history_messages.clone());
        let parameters = para_builder.build().map_err(|e| MyError::ChatCompletionError{error: e})?;
        let answer = call_tool_not_use_stream(&uuid, client.clone(), parameters).await?;
        // if answer is call tool result, continue; else break
        match answer {
            CallToolResult::CallTool((raw_message, call_tool_result)) => { // (ChatMessage, Vec<(tool name, tool args, call tool id, content)>)
                is_first = true;
                for j in call_tool_result {
                    // call tool
                    let mut name_id: Vec<&str> = j.0.split("__").collect();
                    if name_id.len() < 2 && name_id[0] != "activate_skill" {
                        if let Some(tools_vec) = tool_map.get(&j.0) {
                            if tools_vec.len() > 1 {
                                event!(Level::WARN, "{} call tool error, llm only return tool name prefix `{}`, but this prefix have multiple tools: {}", uuid, j.0, tools_vec.join(", "));
                            } else {
                                real_name = tools_vec[0].clone();
                                name_id = real_name.split("__").collect();
                                event!(Level::WARN, "{} call real tool `{}` by model returned `{}`", uuid, real_name, j.0);
                            }
                        } else {
                            event!(Level::WARN, "{} can't find real tool by model returned `{}`", uuid, j.0);
                        }
                    }
                    // 模型返回的调用tool的json格式参数可能有问题，导致调用tool时解析参数失败，这里修复下可能存在的json格式问题
                    let mut safe_args = j.1
                        .replace(": \"[", ": [") // `: "[` --> `: ["`
                        .replace("]\"", "]"); // `]"` --> `]`
                        //.replace(": \"[\\\"", ": [\"") // `"[\"` --> `["`
                        //.replace("\\\"]\"", "\"]") // `"\"]"` --> `"]`
                        //.replace("\\\", ", "\", ") // `\\\", ` --> `\", `
                        //.replace(", \\\"", ", \"") // `, \\\"` --> `, \"`
                    if name_id[0].starts_with("edit_file") {
                        safe_args = safe_args
                            .replace("{\\\"newText\\\": \\\"", "{\"newText\": \"") // `{\"newText\": \"` --> `{"newText": "`
                            .replace("\\\"}, {\\\"newText\\\": \\\"", "\\\"}, {\"newText\": \"") // `\"}, {\"newText\": \"` --> `"}, {"newText": "`
                            .replace("\\\", \\\"oldText\\\": \\\"", "\", \"oldText\": \"") // `\", \"oldText\": \"` --> `", "oldText": "`
                            .replace("\\\"}]}", "\"}]}"); // `\"}]}` --> `"}]}`
                    }
                    if name_id[0].starts_with("write_file") || name_id[0].starts_with("edit_file") {
                        let sep = if name_id[0].starts_with("write_file") { "\"content\":" } else { "\"edits\":" };
                        if let Some((first_part, second_part)) = safe_args.split_once(sep) {
                            let re = Regex::new(r#", \\\"(.*?)\\\""#).unwrap();
                            let mut tmp = re.replace_all(first_part, r#", "$1""#).to_string();
                            let re = Regex::new(r#"\\\"(.*?)\\\", "#).unwrap();
                            tmp = re.replace_all(&tmp, r#""$1", "#).to_string();
                            let re = Regex::new(r#"\[\\\"(.*?)\\\\]", "#).unwrap();
                            tmp = re.replace_all(&tmp, r#"["$1"]"#).to_string();
                            tmp += sep;
                            tmp += second_part;
                            safe_args = tmp;
                        }
                    } else {
                        let re = Regex::new(r#", \\\"(.*?)\\\""#).unwrap();
                        safe_args = re.replace_all(&safe_args, r#", "$1""#).to_string();
                        let re = Regex::new(r#"\\\"(.*?)\\\", "#).unwrap();
                        safe_args = re.replace_all(&safe_args, r#""$1", "#).to_string();
                        let re = Regex::new(r#"\[\\\"(.*?)\\\\]", "#).unwrap();
                        safe_args = re.replace_all(&safe_args, r#"["$1"]"#).to_string();
                        //let re = Regex::new(r#"\\\"(.*?)\\\""#).unwrap();
                        //safe_args = re.replace_all(&safe_args, r#""$1""#).to_string();
                    }
                    //println!("raw  args: {:?}", j.1);
                    //println!("safe args: {:?}", safe_args);
                    let (result, language) = match try_call_tool(&uuid, &name_id, &safe_args, j.3.clone(), sender.clone()).await {
                        Ok(inner_result) => {
                            match inner_result {
                                Ok((result, file_option)) => {
                                    try_count = 0;
                                    (result, get_file_language(file_option))
                                },
                                Err(e) => {
                                    try_count += 1;
                                    if is_first {
                                        if try_count >= 3 {
                                            return Err(e)
                                        } else {
                                            event!(Level::WARN, "{} call tool {} error, try again {}\n{}\n\nraw args: {}\n\nsafe args: {}", uuid, name_id[0], try_count, e, j.1, safe_args);
                                            continue 'outer
                                        }
                                    } else {
                                        event!(Level::WARN, "{} call tool {} error, not try again, return to model\n{}\n\nraw args: {}\n\nsafe args: {}", uuid, name_id[0], try_count, j.1, safe_args);
                                        try_count = 0;
                                        (format!("call tool {} error: {}", name_id[0], e), "text".to_string())
                                    }
                                },
                            }
                        },
                        Err(e) => {
                            event!(Level::WARN, "{} call tool {}, raw args: {}", uuid, name_id[0], j.1);
                            event!(Level::WARN, "{} call tool {}, safe args: {}", uuid, name_id[0], safe_args);
                            return Err(e)
                        },
                    };
                    call_tool_count += 1;

                    // 1. send call tool result to user page
                    let messages_num = get_messages_num(&uuid); // 流式输出传输答案时，答案还未插入到服务端记录中，因此这里获取总消息数不需要减1
                    // uuid, id, content, is_left, is_img, is_voice, is_history, is_web, time_model, current_token
                    let test_result = if result.contains("```") {
                        result.clone()
                    } else {
                        format!("```{}\n{}\n```", language, result)
                    };
                    let test_result = if let Some(content) = &j.3 {
                        if content.is_empty() {
                            format!("## 📌 {} call tool\n\n---\n\n### 🛠 run tool\n{}({})\n\n### 💡 result\n{}", call_tool_count, name_id[0], &j.1, test_result)
                        } else {
                            format!("## 📌 {} call tool\n\n---\n\n{}\n\n---\n\n### 🛠 run tool\n{}({})\n\n### 💡 result\n{}", call_tool_count, content, name_id[0], &j.1, test_result)
                        }
                    } else {
                        format!("## 📌 {} call tool\n\n---\n\n### 🛠 run tool\n{}({})\n\n### 💡 result\n{}", call_tool_count, name_id[0], &j.1, test_result)
                    };
                    if let Err(e) = sender.send(MainData::prepare_sse(&uuid, messages_num, test_result.replace("\n", "srxtzn"), true, false, false, false, false, None, Some(0), None, name_id[0] == "edit_file")?).await { // 传递数据以`data: `起始，以`\n\n`终止
                        event!(Level::WARN, "channel send error: {:?}", e);
                        break
                    }

                    // 2. add result to main message history
                    let message = ChatMessage::Assistant{
                        content: Some(ChatMessageContent::Text(test_result)),
                        reasoning_content: None,
                        refusal: None,
                        name: None,
                        audio: None,
                        tool_calls: None,
                    };
                    let tmp_time = Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); // 回答的当前时间，例如：2024-10-21 16:35:47
                    insert_message(&uuid, message, None, tmp_time, false, DataType::Normal, None, model, None);

                    // 3. 显示在页面的信息，包括：当前uuid、当前uuid的问题和答案的总token数、当前uuid的prompt名称、与当前uuid相关的所有uuid
                    let meta_data = MetaData::new(uuid.clone(), None);
                    if let Err(e) = sender.send(meta_data.prepare_sse(&uuid)?).await { // 传递数据以`data: `起始，以`\n\n`终止
                        event!(Level::WARN, "channel send error: {:?}", e);
                        break
                    }

                    // add each tool call result to current message history
                    if is_first {
                        history_messages.push(raw_message.clone());
                        is_first = false;
                    }
                    history_messages.push(ChatMessage::Tool{content: ChatMessageContent::Text(result), tool_call_id: j.2});
                }
            },
            CallToolResult::Text(test_result) => { // normal text result, not call tool
                // 1. send to user page
                let messages_num = get_messages_num(&uuid); // 流式输出传输答案时，答案还未插入到服务端记录中，因此这里获取总消息数不需要减1
                // uuid, id, content, is_left, is_img, is_voice, is_history, is_web, time_model, current_token
                if let Err(e) = sender.send(MainData::prepare_sse(&uuid, messages_num, test_result.replace("\n", "srxtzn"), true, false, false, false, false, None, Some(0), None, false)?).await { // 传递数据以`data: `起始，以`\n\n`终止
                    event!(Level::WARN, "channel send error: {:?}", e);
                }
                // 2. add result to main message history
                let message = ChatMessage::Assistant{
                    content: Some(ChatMessageContent::Text(test_result)),
                    reasoning_content: None,
                    refusal: None,
                    name: None,
                    audio: None,
                    tool_calls: None,
                };
                let tmp_time = Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); // 回答的当前时间，例如：2024-10-21 16:35:47
                insert_message(&uuid, message.clone(), None, tmp_time, false, DataType::Normal, None, model, None);

                break
            },
        }
        if call_tool_count > call_tool_limit {
            event!(Level::WARN, "{} already call tool {} times, stop this loop", uuid, call_tool_count);
            break
        }
    }
    // page left info
    let meta_data = MetaData::new(uuid.clone(), None);
    if let Err(e) = sender.send(meta_data.prepare_sse(&uuid)?).await { // 传递数据以`data: `起始，以`\n\n`终止
        event!(Level::WARN, "channel send error: {:?}", e);
    }
    Ok(())
}

/// 调用的skill
#[derive(Deserialize)]
struct SkillParams {
    skill_name: String,
}

/// try run tool, if error not from call tool, return Err(), else return Ok(Ok()) or Ok(Err())
async fn try_call_tool(uuid: &str, name_id: &[&str], paras: &str, info: Option<String>, sender: Sender<Vec<u8>>) -> Result<Result<(String, Option<String>), MyError>, MyError> {
    if name_id[0] == "activate_skill" {
        let params: SkillParams = serde_json::from_str(paras).map_err(|e| MyError::SerdeJsonFromStrError{error: e})?;
        Ok(Ok((PARAS.skills.get_skill_full_content(&params.skill_name)?, None)))
    } else if name_id.len() < 2 {
        return Ok(Err(MyError::ToolNotExistError{id: name_id[0].to_string(), info: "run_tools".to_string()}))
    } else if PARAS.tools.contain_tool_id(name_id[1]) {
        if PARAS.approval_all {
            Ok(PARAS.tools.run(name_id[1], paras))
        } else {
            match PARAS.tools.get_approval(name_id[1], paras, info, PARAS.english)? {
                Some(approval_msg) => {
                    let approval_msg = if name_id[0] == "edit_file" {
                        let mut params: Params = match serde_json::from_str(paras) {
                            Ok(p) => p,
                            Err(e) => return Ok(Err(MyError::SerdeJsonFromStrError{error: e})),
                        };
                        params.dry_run = Some(true);
                        let dry_run_para = match serde_json::to_string(&params) {
                            Ok(d) => d,
                            Err(e) => return Ok(Err(MyError::JsonToStringError{error: e.into()})),
                        };
                        match PARAS.tools.run(name_id[1], &dry_run_para) {
                            Ok(r) => r.0,
                            Err(e) => return Ok(Err(e)),
                        }
                    } else if name_id[0] == "write_file" {
                        approval_msg.chars().take(100).collect() // 截取显示100个字符，否则弹窗很高，无法点击同意或拒绝
                    } else {
                        approval_msg
                    };
                    if ask_approval(&uuid, approval_msg, name_id[0] == "edit_file", sender).await? {
                        Ok(PARAS.tools.run(name_id[1], paras))
                    } else {
                        return Err(MyError::PlanModeError{info: format!("Not allowed to call this tool: {}", name_id[0])})
                    }
                },
                None => Ok(PARAS.tools.run(name_id[1], paras)),
            }
        }
    } else if PARAS.mcp_servers.contain_server_id(name_id[1]) {
        Ok(PARAS.mcp_servers.run(&name_id, paras).await)
    } else {
        return Ok(Err(MyError::ToolNotExistError{id: name_id[1].to_string(), info: "run_tools".to_string()}))
    }
}

/// https://github.com/microsoft/TaskWeaver/blob/main/taskweaver/planner/planner_prompt.yaml
/// https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools/blob/main/Augment%20Code/claude-4-sonnet-agent-prompts.txt
/// https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools/blob/main/Augment%20Code/gpt-5-agent-prompts.txt
/// https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools/blob/main/Open%20Source%20prompts/Codex%20CLI/openai-codex-cli-system-prompt-20250820.txt
/// https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools/blob/main/Qoder/Quest%20Action.txt
/// https://github.com/potpie-ai/potpie/blob/main/app/modules/intelligence/agents/chat_agents/pydantic_multi_agent.py
/// https://github.com/potpie-ai/potpie/blob/main/app/modules/intelligence/agents/custom_agents/custom_agents_service.py
/// https://github.com/evalstate/fast-agent/blob/main/src/fast_agent/agents/workflow/iterative_planner.py
/// https://github.com/evalstate/fast-agent/blob/main/src/fast_agent/agents/workflow/orchestrator_prompts.py

const PLAN_FORMAT_PROMPT: &str = r###"
# Output Format
**Always** respond in strict JSON format, structured as follows:

{
    "steps": [
        {
            "description": "string",
            "status": "string", // 'pending', 'in_progress', 'completed', 'update_plan', 'failed'
            "tool_name": "string | null",
            "ask_approval": "boolean",
            "result": "string"
        }
        // ... more steps
    ],
    "all_steps_completed": "boolean",
    "final_result": "string",
    "error_msg": ""
}

Example response (step 1 and 2 have been completed, next step is steps 3, step 4 is planned but pending):

{
    "steps": [
        {
            "description": "Read the content of the source data file.",
            "status": "completed",
            "tool_name": "read_file",
            "ask_approval": false,
            "result": "Successfully read 'data.csv', it contains headers and 100 rows of data.",
        },
        {
            "description": "analyze the structure of the data to confirm it's tab-separated.",
            "status": "completed",
            "tool_name": null,
            "ask_approval": false,
            "result": "The file 'data.csv' uses a comma as a delimiter, not a tab. The plan will be updated.",
        },
        {
            "description": "Process the data using a comma delimiter and generate a summary.",
            "status": "in_progress",
            "tool_name": "data_analyzer",
            "ask_approval": false,
            "result": "",
        },
        {
            "description": "Write the generated summary to a new file named 'summary.txt'.",
            "status": "pending",
            "tool_name": "write_file",
            "ask_approval": true,
            "result": "",
        },
    ],
    "all_steps_completed": false,
    "final_result": "",
    "error_msg": "",
}

# Error Response Format
If ANY step cannot be executed due to missing tools or capabilities, return:

{
    "steps": [],
    "all_steps_completed": false,
    "final_result": "",
    "error_msg": "Cannot complete the task because [specific step description] requires [required capability/tool] which is not available."
}

Example debug code plan:

{
    "steps": [
        {
            "description": "Read the content of the source code file to examine its current code.",
            "status": "in_progress",
            "tool_name": "read_file",
            "ask_approval": false,
            "result": "content of source code file",
        },
        {
            "description": "Analyze the source code to identify any syntax errors, logical flaws, or runtime issues.",
            "status": "pending",
            "tool_name": "null",
            "ask_approval": false,
            "result": "errors, logical flaws, or runtime issues of source code.",
        },
        {
            "description": "Generate a corrected version of code with the identified issues fixed.",
            "status": "pending",
            "tool_name": "null",
            "ask_approval": false,
            "result": "issues fixed code.",
        },
        {
            "description": "Update the source code file with the corrected code using the edit_file tool.",
            "status": "pending",
            "tool_name": edit_file,
            "ask_approval": true,
            "result": "",
        },
    ],
    "all_steps_completed": false,
    "final_result": "",
    "error_msg": "",
}

"###;

const UPDATE_PLAN_PROMPT: &str = r###"
# Update Previous Plans

You do not create new plans, you intelligently modify the current one based on new information.

A step in the current plan has just been marked with `status: "update_plan"`. This is your signal to intervene.

You must analyze the reason for the failure and intelligently revise the existing plan.

1. **Analyze the Failure**:
    - Locate the step in the `steps` array where `status` is `"update_plan"`.
    - Read its `result` field carefully. This field contains the critical reason why the original plan failed (e.g., a tool error, unexpected data, a dead end).

2. **Diagnose and Revise the Plan**:
    - Based on the failure reason and previous step results, evaluate the rest of the plan. How does this failure impact subsequent steps?
    - You may need to perform one or more of the following actions on the `steps` array:
        - **Modify**: Change the `description` or `tool_name` of a future step to accommodate the new reality.
        - **Insert**: Add one or more new steps before the failed step to rectify the issue (e.g., add a data cleaning step if the data format is wrong).
        - **Delete**: Remove steps that are no longer relevant or possible.
        - **Re-order**: Change the sequence of steps if a different approach is now required.
    - Your revision must directly address the failure and create a viable path towards the original goal.

3.  **Reset Execution Status**:
    - Change the failed step's `status` from `"update_plan"` to `failed`. This serves as a record of what went wrong. Ensure its `result` clearly states the failure reason.
    - Identify the **new next step to be executed** in your revised plan.
    - Set the `status` of this new next step to `"in_progress"`.
    - Ensure all subsequent steps in your revised plan have their `status` set to `"pending"`.
    - Keep `all_steps_completed` as `false`.
    - Keep `final_result` as `""`.

# Revision Principles

- **Minimal Change**: Make the smallest possible modification that will fix the plan. Avoid wholesale rewrites unless absolutely necessary.
- **Goal Alignment**: Your revised plan must still aim to solve the original user task.
- **Logical Flow**: The new sequence of steps must be logical and coherent.

# Input

<chatsong:current-plan-status>
--current-plans--
</chatsong:current-plan-status>
"###;

const PLANER_PROMPT: &str = r###"
You are an advanced task planning specialist who break down complex multi-step task into comprehensive sequential plans.

# Planning Principles (STRICT ENFORCEMENT)
When creating or updating a plan, you must adhere to the following strict rules. **Violation of these rules will result in plan failure.**
  1. **Strict Tool Dependency**: You act within a "Closed World" regarding external actions. You **cannot** perform any Input/Output (I/O) operations (e.g., reading files, writing files, saving data, searching the web, sending emails) unless a specific tool is provided in `<chatsong:available-tools>`.
  2. **Internal vs. External Actions**:
     - **Internal Actions (Tool = null)**: You MAY perform pure reasoning, text summarization, formatting, or logic calculation using your internal knowledge.
     - **External Actions (Tool MUST exist)**: You MUST verify a tool exists for any step involving file manipulation, data retrieval, or persistent storage.
  3. **The "Missing Tool" Protocol**: If a necessary step requires an External Action (e.g., "save to file") but no corresponding tool exists in the provided list `<chatsong:available-tools>`:
     - You **MUST NOT** pretend you can do it.
     - You **MUST NOT** assign `tool_name: null` to that step.
     - You **MUST** immediately abort the entire planning process and return the **Error JSON**.
  4. **Predictive Logical Sequence (CRITICAL)**:
     - The order of steps must respect dependencies.
     - You must generate a **comprehensive initial plan** that covers the entire lifecycle of the task.
     - **Do NOT stop** the plan after information gathering (e.g., "list directories" or "search").
     - **Always anticipate the next steps**: If a step gathers information (e.g., finding a file path), you **MUST** immediately follow it with steps that use that information (e.g., reading that file), assuming the information will be successfully retrieved.
     - Example: If the task is "Fix a bug", the plan must include: 1. Find file -> 2. Read file -> 3. Modify file -> 4. Save file.
  5. **Atomicity**: Each step should be focused on a single, clear, and indivisible action.
  6. **Tool Matching**: Specify a `tool_name` only when a step genuinely requires it, and ensure the tool name is correct and exists.
  7. **Status-Independent Tooling**: The `tool_name` for a step is determined at the planning stage based on the step's intrinsic need for a tool. You MUST specify the correct `tool_name` for any step that requires a tool, regardless of whether its status is `pending`, `in_progress`, or `completed`. A `pending` status only means "waiting to be executed," not "tool undetermined".

# Capability Boundaries
**CRITICAL**: You are a text-processing engine. You do NOT have a file system, you do NOT have a browser, and you do NOT have a command line unless a Tool provides that interface.
  - **Example of Impossible Action**: "Save the result to `abc.txt`" -> If no `save_file` or `write_file` tool is provided, this step is **IMPOSSIBLE**. You cannot "just do it."
  - **Example of Possible Action**: "Summarize the text read in step 1" -> This is **POSSIBLE** as an internal action (`tool_name: null`).

# Persona and Objective
  - The content of your plan should not involve doing anything that you aren't capable of doing.
  - Do not use plans for simple or single-step queries that you can just do or answer immediately.
  - Each step should contains 5 properties:
    - description: clear purpose of this step.
    - status: progress status, include `in_progress`, `pending`, `completed`, `update_plan`, `failed`.
    - tool_name: specify the Tool name precisely if need use Tools.
    - ask_approval: a boolean flag indicating whether user confirmation is required before executing this step.
      - Set to `true` ONLY if the operation involves modifying system state, specifically: creating, modifying, or deleting files.
      - Set to `false` for read-only operations (e.g., reading files, searching, querying), computational tasks, or steps that do not involve any tool usage.
    - result: result of this step.
  - Set the `status` of the **first step** to `in_progress`.
  - Set the `status` of **all other steps** to `pending`.

--update-plan--

# Step Attribute Definitions
- `description`: **String**. A concise, clear description of the step's purpose and action.
- `status`: **String**. The current state of the step. Must be one of the following:
    - `pending`: Waiting to be executed. Used for steps that are planned but not yet started.
    - `in_progress`: Currently being executed. Only one step may have this status at any time.
    - `completed`: Successfully finished. The `result` field should contain the outcome of the execution.
    - `update_plan`: Execution failed or based on previous step results, update plans. The `result` field should contain the specific reason.
- `tool_name`: **String or Null**. The exact name of the tool to be called for this step. Use `null` if no tool is required.
- `result`: **String**. The outcome or status note of the step.
    - When `status` is `in_progress` or `pending`, this field must be an empty string `""`.
    - When `status` is `completed`, this field records the step's output (e.g., file content, data analysis summary).
    - When `status` is `update_plan` or `failed`, this field records the reason.

# Available Tools and Constraints
<chatsong:available-tools>
--tools--
</chatsong:available-tools>


**CRITICAL WARNING**:
  - You **MUST** and **CAN ONLY** use tool names that are **EXACTLY** as listed in the `<chatsong:available-tools>` section above.
  - Do not use non-existent tools or modify tool names. The plan will fail if you do.
  - If the user asks to "save", "write", or "download" something, check the tools list. If no matching tool is found, you MUST return the `error_msg`.
--plan-format-prompt--
"###;

const CONTINUE_OR_UPDATE_PROMPT: &str = r###"
You are a Task Execution Controller, an AI specialist responsible for managing the execution of a multi-step plan. Your sole function is to receive the current status of a plan, evaluate the result of the most recently completed step, and make one of three decisions: **CONTINUE** to the next step, **UPDATE** the plan, or **TERMINATE** the plan as complete.

You are a logical and deterministic decision-maker. You do not add commentary; you only update the provided JSON state based on the rules below.

# Core Decision Logic

You will receive a JSON plan containing one step with `status: "in_progress"`. Your primary task is to analyze results from the previous steps already executed, and evaluate the `result` of that specific step and decide the next action.

1.  **Locate the Current Step**: Find the step in the `steps` array where `status` is `"in_progress"`.

2.  **Evaluate and Decide**: Analyze the `result` of the current step.

    - **Scenario A: Continue to Next Step**
        **Condition**: The `result` meets the objective of the current step, and the plan remains viable.
        **Detailed Logic**:
            1.  **For Analysis/Check Steps**: If the step is to "analyze", "check", "review", or "investigate", a result that lists "errors", "bugs", "issues", or "critical problems" is considered a **SUCCESSFUL OUTPUT**. Finding these issues implies the analysis step worked correctly and the next step (likely a fix) should proceed. Do **NOT** treat the discovery of bugs as a reason to update the plan unless the plan lacks a step to fix them.
            2.  **For Tool Calls**: The tool returned meaningful data without execution failures (e.g., timeouts, API errors, or "File not found").
            3.  **For General Steps**: The step's objective was achieved, even if the outcome reveals issues that need to be addressed in subsequent steps.
        **Action**:
            1.  Change the `status` of the current step from `"in_progress"` to `"completed"`.
            2.  Find the **next** step in the array (the one immediately following) whose `status` is `"pending"`.
            3.  Change that next step's `status` from `"pending"` to `"in_progress"`.
            4.  Keep `all_steps_completed` as `false`.
            5.  Keep `final_result` as `""`.

    - **Scenario B: Update the Plan**
        **Condition**: The `result` indicates a **failure to perform the current step**, or reveals that the **subsequent plan is logically impossible or unnecessary**.
        **Detailed Logic**:
            1.  **Execution Failure**: The tool crashed, timed out, returned a system error, or could not be accessed (e.g., "File not found" when trying to read).
            2.  **Empty/Unexpected Data**: Data was required to proceed, but the result was empty or in a completely unexpected format that the next step cannot handle.
            3.  **Plan Obsolescence**: The result shows that the goal is already achieved (e.g., "File is already correct" before a fix step), making the next steps unnecessary.
            4.  **Crucial Distinction**: If the result simply describes problems *within the target* (e.g., "Syntax error found in script"), but the current step was just to *analyze* it, this is **NOT** a reason to update the plan. This is a successful analysis. Only update if the *act of analyzing* failed or the next step cannot be performed.
        **Action**:
            1.  Change the `status` of the current step from `"in_progress"` to `"update_plan"`.
            2.  Ensure the `result` field clearly and concisely describes the reason for the update (e.g., "Tool 'search_web' failed with API error.", "Result was empty, cannot proceed.", "File content shows a different format than expected, requires new plan.").
            3.  Keep all other steps' status as they are.
            4.  Keep `all_steps_completed` as `false`.
            5.  Keep `final_result` as `""`.

    - **Scenario C: Terminate Plan (All Steps Completed)**
        - **Condition**: This is a special case of Scenario A. After successfully completing the final step of the plan, there are no more `"pending"` steps.
        - **Action**:
            1.  Change the status of the final step from `"in_progress"` to `"completed"`.
            2.  Set `all_steps_completed` to `true`.
            3.  Synthesize the `result` from all completed steps into a comprehensive final answer and place it in the `final_result` field.

# Input

You will receive the following context to make your decision:

<chatsong:current-plan-status>
--current-plans--
</chatsong:current-plan-status>

# Output
return the updated JSON
"###;

/// step status
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
enum Status {
    Pending,    // waiting to be executed
    InProgress, // in progress
    Completed,  // completed step
    UpdatePlan, // update plan because this step
    Failed,     // failed step
}

impl Status {
    /// convert to string
    fn to_string(&self) -> String {
        match self {
            Self::Pending    => "pending",
            Self::InProgress => "in progress",
            Self::Completed  => "completed",
            Self::UpdatePlan => "update plan",
            Self::Failed     => "failed",
        }.to_string()
    }
}

/// single step
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Step {
    description:  String,
    status:       Status,
    tool_name:    Option<String>,
    #[serde(default)]
    ask_approval: bool,
    result:       String,
}

impl Step {
    fn format_step(&self, num: usize) -> String {
        format!("step{} subtask: {}\nstep{} result: {}", num, self.description, num, self.result)
    }
}

/// plan
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Plan {
    steps:               Vec<Step>,
    all_steps_completed: bool,
    final_result:        String,
    error_msg:           String,
}

impl Plan {
    /// convert json string to Plan
    fn from_str(s: &str) -> Result<Self, MyError> {
        let json_str = match (s.starts_with("```json"), s.ends_with("```")) {
            (true, true) => s.strip_prefix("```json").unwrap().strip_suffix("```").unwrap(),
            (true, false) => s.strip_prefix("```json").unwrap(),
            (false, true) => s.strip_suffix("```").unwrap(),
            (false, false) => s,
        };
        match serde_json::from_str(json_str.trim()) {
            Ok(p) => Ok(p),
            Err(e) => {
                event!(Level::ERROR, "json string to Plan struct: {}", json_str);
                Err(MyError::SerdeJsonFromStrError{error: e})
            },
        }
    }

    /// format plan
    fn format_plan(&self, init: bool) -> String {
        let mut result: Vec<String> = Vec::new();
        for (i, p) in self.steps.iter().enumerate() {
            match &p.tool_name {
                Some(t) => {
                    let name_id: Vec<&str> = t.split("__").collect();
                    if init {
                        result.push(format!("{}. [tool: {}] {}", i+1, name_id[0], p.description));
                    } else {
                        result.push(format!("{}. [status: {}] [tool: {}] {}", i+1, p.status.to_string(), name_id[0], p.description));
                    }
                },
                None => {
                    if init {
                        result.push(format!("{}. {}", i+1, p.description));
                    } else {
                        result.push(format!("{}. [status: {}] {}", i+1, p.status.to_string(), p.description));
                    }
                }
            }
        }
        result.join("\n")
    }

    /// get each step description and result for next step
    fn format_steps(&self, idx: usize) -> String {
        let mut result: Vec<String> = Vec::new();
        for (i, s) in self.steps[0..idx].iter().enumerate() {
            result.push(s.format_step(i+1));
        }
        let final_result = result.join("\n\n");
        if final_result.is_empty() {
            final_result
        } else {
            final_result+"\n\n"
        }
    }
}

/// first make a plan, then chat with tools: built-in + external tools
/// 1. make plan
/// 2. post each plan step to LLM api
/// 3. based on result, continue or update plan
/// 4. all plan steps have been finished, send final result to uer, record this message
/// Only the final result will be recorded, the intermediate context messages will be discarded.
/// +-----------------------------------------------------------------------------------+
/// |                     question                                                      |
/// |                        |                                                          |
/// |                        V                                                          |
/// |                 make/update plan <----------------------------------------------. |
/// |                        |                                                        | |
/// | +----------------------+--------------------+                                   | |
/// | |                      |                    |                                   | |
/// | |                      V                    |                                   | |
/// | | .-------------> step by step              |                                   | |
/// | | |                    |                    |                                   | |
/// | | |                    V                    |                                   | |
/// | | |                   LLM <-----------------+------------------.                | |
/// | | |                    |                    |                  |                | |
/// | | |                    V                    |                  |                | |
/// | | |                call tool ?              |                  |                | |
/// | | |                    |                    |                  |                | |
/// | | |                +-------+                |                  |                | |
/// | | |                |       |                |    +----------------------------+ | |
/// | | |                V       V                |    | run tool, push function    | | |
/// | | |                No      Yes -------------+--> | calling result to function | | |
/// | | |                |                        |    | calling history            | | |
/// | | |                V                        |    +----------------------------+ | |
/// | | |          update plan ?                  |                                   | |
/// | | |                |                        |                                   | |
/// | | |            +-------+                    |                                   | |
/// | | |            No      Yes -----------------+-----------------------------------' |
/// | | |            |                            |                                     |
/// | | |            V                            |                                     |
/// | | |    +----------------------------------+ |                                     |
/// | | '--- | push current step result to main | |                                     |
/// | |      | history, continue next step      | |                                     |
/// | |      +----------------------------------+ |                                     |
/// | +-------------------------------------------+                                     |
/// |                        |                                                          |
/// |                        V                                                          |
/// |          +---------------------------+                                            |
/// |          | return final result, push |                                            |
/// |          | result to global history  |                                            |
/// |          +---------------------------+                                            |
/// +-----------------------------------------------------------------------------------+
/// parameters:
/// +--------------------------------------------------------------------+
/// | selected_tools: html pulldown selected tools                       |
/// | uuid:           current chat uuid                                  |
/// | sender:         channel for send final result to user page         |
/// | client:         OpenAI api Client for send request                 |
/// | para_builder:   update history messages before send request to LLM |
/// | model:          model name                                         |
/// +--------------------------------------------------------------------+
/// function calling:
/// https://api-docs.deepseek.com/zh-cn/guides/function_calling
/// https://docs.bigmodel.cn/cn/guide/capabilities/function-calling
/// https://platform.moonshot.cn/docs/api/tool-use
pub async fn run_tools_with_plan(selected_tools: Option<SelectedTools>, uuid: String, sender: Sender<Vec<u8>>, client: Client, para_builder: ChatCompletionParametersBuilder, model: &str) -> Result<(), MyError> {
    // get built-in and external tools schame
    let mut tool_schema = PARAS.tools.get_desc_and_schema(&selected_tools)?;
    // get mcp tools schema
    let mcp_schema = PARAS.mcp_servers.get_desc_and_schema(&selected_tools).await?;

    tool_schema.extend(mcp_schema);
    //let tool_schema_json = serde_json::to_string(&tool_schema).map_err(|e| MyError::JsonToStringError{error: e.into()})?;
    let tool_schema_json = tool_schema.iter().map(|t| format!("tool: {}, description: {:?}", t.function.name, t.function.description)).collect::<Vec<String>>().join("\n");
    let plan_prompt = PLANER_PROMPT.replace("--tools--", &tool_schema_json).replace("--plan-format-prompt--", PLAN_FORMAT_PROMPT);
    let history_messages: Vec<ChatMessage> = get_messages(&uuid); // store all messages as context log, this is temp history, will not add to the user's main history

    // make a new plan
    let mut plan_string = make_update_plan(uuid.clone(), client.clone(), para_builder.clone(), model, history_messages.clone(), &plan_prompt, None).await?;
    let mut first_step = true;
    let mut plan_struct: Plan;
    let mut max_update_plan = 0;
    //println!("\n{:?}\n", plan_string);

    loop {
        plan_struct = Plan::from_str(&plan_string)?;
        if first_step {
            let msg = format!("## 🚩 make plan\n\n---\n\n{}", plan_struct.format_plan(true));
            send_and_record_message(&uuid, msg, 0, model, sender.clone(), false).await?;
            first_step = false;
        }
        if plan_struct.all_steps_completed && plan_struct.steps.iter().any(|s| s.status == Status::Pending) {
            plan_struct.all_steps_completed = false;
        }
        if !plan_struct.error_msg.is_empty() {
            let msg = format!("## 🤔 error\n\n---\n\n{}", plan_struct.error_msg);
            send_and_record_message(&uuid, msg, plan_struct.steps.len()+1, model, sender.clone(), false).await?;
            break
        } else if plan_struct.all_steps_completed || plan_struct.steps.iter().all(|s| s.status == Status::Completed) {
            let msg = format!("## 📌 final result\n\n---\n\n{}", plan_struct.final_result);
            send_and_record_message(&uuid, msg, plan_struct.steps.len()+1, model, sender.clone(), false).await?;
            break
        } else {
            if !plan_struct.steps.iter().any(|s| s.status == Status::InProgress || s.status == Status::UpdatePlan) {
                if plan_struct.steps.iter().any(|s| s.status == Status::Pending) {
                    event!(Level::WARN, "all steps not contain \"in_progress\" or \"update_plan\", change first \"Pending\" to \"InProgress\"");
                    for i in 0..plan_struct.steps.len() {
                        if plan_struct.steps[i].status == Status::Pending {
                            plan_struct.steps[i].status = Status::InProgress;
                        }
                    }
                }
            }
            for i in 0..plan_struct.steps.len() {
                match &plan_struct.steps[i].status {
                    Status::Pending | Status::Completed | Status::Failed => (),
                    Status::InProgress => {
                        let mut tool_name = "".to_string();
                        let mut edit_file_result = "".to_string();
                        if let Some(t) = &plan_struct.steps[i].tool_name {
                            tool_name = t.clone();
                            let step_tools = match tool_schema.iter().find(|item| &item.function.name == t) {
                                Some(s) => vec![s.clone()],
                                None => {
                                    match tool_schema.iter().find(|item| item.function.name.starts_with(&format!("{}__", t))) {
                                        Some(s) => {
                                            event!(Level::WARN, "step use tool: {} ({})", t, s.function.name);
                                            vec![s.clone()]
                                        },
                                        None => {
                                            event!(Level::ERROR, "step tool_name not correct: {} ({})", t, tool_schema.iter().map(|iterm| iterm.function.name.clone()).collect::<Vec<_>>().join(", "));
                                            return Err(MyError::PlanModeError{info: format!("step tool_name not correct: {}", t)})
                                        },
                                    }
                                },
                            };
                            let step_messages = vec![
                                ChatMessage::User{
                                    content: ChatMessageContent::Text(format!("{}current step subtask: {}", plan_struct.format_steps(i), plan_struct.steps[i].description)),
                                    name: None,
                                }
                            ];
                            for j in 0..3 {
                                match function_calling(
                                    step_tools.clone(),
                                    step_messages.clone(),
                                    &uuid,
                                    client.clone(),
                                    para_builder.clone(),
                                    sender.clone(),
                                    plan_struct.steps[i].ask_approval,
                                    //model,
                                ).await? {
                                    Ok(r) => {
                                        if t == "edit_file" || t.starts_with("edit_file__") {
                                            plan_struct.steps[i].result = format!("successfully edit file:\n{}", r);
                                            edit_file_result = r;
                                        } else {
                                            plan_struct.steps[i].result = r;
                                        }
                                        break
                                    },
                                    Err(e) => {
                                        if j == 2 {
                                            return Err(e)
                                        } else {
                                            event!(Level::WARN, "{} call tool {} error, try again {}", uuid, t, j+1);
                                        }
                                    },
                                }
                            }
                        }
                        plan_string = serde_json::to_string(&plan_struct).map_err(|e| MyError::JsonToStringError{error: e.into()})?;
                        plan_string = make_decision(uuid.clone(), client.clone(), para_builder.clone(), model, history_messages.clone(), &plan_string).await?;
                        let mut plan_struct_new = Plan::from_str(&plan_string)?;
                        if plan_struct_new.steps[i].result.is_empty() {
                            plan_struct_new.steps[i].status = Status::UpdatePlan;
                            plan_string = serde_json::to_string(&plan_struct_new).map_err(|e| MyError::JsonToStringError{error: e.into()})?;
                        } else {
                            let msg = if plan_struct_new.steps[i].result.contains("```") {
                                if edit_file_result.is_empty() {
                                    plan_struct_new.steps[i].result.clone()
                                } else {
                                    edit_file_result
                                }
                            } else {
                                format!("```\n{}\n```", if edit_file_result.is_empty() { &plan_struct_new.steps[i].result } else { &edit_file_result })
                            };
                            let msg = format!("## 📌 step {}\n\n---\n\n### 📝 description\n{}\n\n### ✨ result\n{}", i+1, plan_struct_new.steps[i].description, msg);
                            send_and_record_message(&uuid, msg, i+1, model, sender.clone(), tool_name == "edit_file" || tool_name.starts_with("edit_file__")).await?;
                        }
                    },
                    Status::UpdatePlan => {
                        if max_update_plan > 5 {
                            event!(Level::WARN, "already update {} times plan, stop this loop", max_update_plan);
                            plan_struct.error_msg = format!("already update {} times plan, stop this loop", max_update_plan);
                            plan_string = serde_json::to_string(&plan_struct).map_err(|e| MyError::JsonToStringError{error: e.into()})?;
                        } else {
                            plan_string = make_update_plan(uuid.clone(), client.clone(), para_builder.clone(), model, history_messages.clone(), &plan_prompt, None).await?;
                            let plan_struct_new = Plan::from_str(&plan_string)?;
                            let msg = format!("## 📌 step {} update plan\n\n---\n\n{}", i+1, plan_struct_new.format_plan(false));
                            send_and_record_message(&uuid, msg, i+1, model, sender.clone(), false).await?;
                            max_update_plan += 1;
                        }
                        break
                    },
                }
            }
        }
    }
    Ok(())
}

/// call LLM
async fn call_llm(messages: Vec<ChatMessage>, uuid: String, client: Client, mut para_builder: ChatCompletionParametersBuilder, model: &str) -> Result<String, MyError> {
    para_builder.messages(messages);
    let parameters = para_builder.build().map_err(|e| MyError::ChatCompletionError{error: e})?;
    Ok(not_use_stream(uuid, client, parameters, model, false).await?.0)
}

/// make a new plan or update provided plan
async fn make_update_plan(uuid: String, client: Client, para_builder: ChatCompletionParametersBuilder, model: &str, mut history_messages: Vec<ChatMessage>, plan_prompt: &str, previous_plan: Option<String>) -> Result<String, MyError> {
    let new_plan_prompt = ChatMessage::User{
        content: ChatMessageContent::Text(match previous_plan {
            Some(plan) => plan_prompt.replace("--update-plan--", &UPDATE_PLAN_PROMPT.replace("--current-plans--", &plan)),
            None => plan_prompt.replace("--update-plan--\n", ""),
        }),
        name: None,
    };
    history_messages.push(new_plan_prompt);
    let result = call_llm(history_messages, uuid, client, para_builder, model).await?;
    Ok(result)
}

/// decision-maker
async fn make_decision(uuid: String, client: Client, para_builder: ChatCompletionParametersBuilder, model: &str, mut history_messages: Vec<ChatMessage>, plan: &str) -> Result<String, MyError> {
    let decision_prompt = ChatMessage::User{
        content: ChatMessageContent::Text(CONTINUE_OR_UPDATE_PROMPT.replace("--current-plans--", plan)),
        name: None,
    };
    history_messages.push(decision_prompt);
    let result = call_llm(history_messages, uuid, client, para_builder, model).await?;
    Ok(result)
}

/// function calling
async fn function_calling(
    step_tools: Vec<ChatCompletionTool>,
    step_messages: Vec<ChatMessage>,
    uuid: &str,
    client: Client,
    mut para_builder: ChatCompletionParametersBuilder,
    sender: Sender<Vec<u8>>,
    step_ask_approval: bool,
    //model: &str
) -> Result<Result<String, MyError>, MyError> {
    let mut final_result = "".to_string();
    // send query to LLM
    para_builder.tools(step_tools);
    para_builder.messages(step_messages.clone());
    let parameters = para_builder.build().map_err(|e| MyError::ChatCompletionError{error: e})?;
    let answer = call_tool_not_use_stream(uuid, client.clone(), parameters).await?;
    match answer {
        CallToolResult::CallTool((_raw_message, call_tool_result)) => { // (ChatMessage, Vec<(tool name, tool args, call tool id, content)>)
            for i in call_tool_result {
                // call tool
                let name_id: Vec<&str> = i.0.split("__").collect();
                let result = if PARAS.tools.contain_tool_id(name_id[1]) {
                    if PARAS.approval_all {
                        match PARAS.tools.run(name_id[1], &i.1) {
                            Ok(r) => r.0,
                            Err(e) => return Ok(Err(e)),
                        }
                    } else if let Some(approval_msg) = PARAS.tools.get_approval(name_id[1], &i.1, i.3.clone(), PARAS.english)? {
                        let approval_msg = if name_id[0] == "edit_file" {
                            let mut params: Params = match serde_json::from_str(&i.1) {
                                Ok(p) => p,
                                Err(e) => return Ok(Err(MyError::SerdeJsonFromStrError{error: e})),
                            };
                            params.dry_run = Some(true);
                            let dry_run_para = match serde_json::to_string(&params) {
                                Ok(d) => d,
                                Err(e) => return Ok(Err(MyError::JsonToStringError{error: e.into()})),
                            };
                            match PARAS.tools.run(name_id[1], &dry_run_para) {
                                Ok(r) => r.0,
                                Err(e) => return Ok(Err(e)),
                            }
                        } else {
                            approval_msg
                        };
                        if ask_approval(uuid, approval_msg, name_id[0] == "edit_file", sender.clone()).await? {
                            match PARAS.tools.run(name_id[1], &i.1) {
                                Ok(r) => r.0,
                                Err(e) => return Ok(Err(e)),
                            }
                        } else {
                            return Err(MyError::PlanModeError{info: format!("Not allowed to call this tool: {}", name_id[0])})
                        }
                    } else {
                        if step_ask_approval {
                            let approval_msg = if PARAS.english {
                                format!("Do you allow calling the {} tool?{}\n{:?}", name_id[0], i.3.clone().unwrap_or_default(), i.1)
                            } else {
                                format!("是否允许调用 {} 工具？{}\n{:?}", name_id[0], i.3.clone().unwrap_or_default(), i.1)
                            };
                            if ask_approval(uuid, approval_msg, false, sender.clone()).await? {
                                match PARAS.tools.run(name_id[1], &i.1) {
                                    Ok(r) => r.0,
                                    Err(e) => return Ok(Err(e)),
                                }
                            } else {
                                return Err(MyError::PlanModeError{info: format!("Not allowed to call this tool: {}", name_id[0])})
                            }
                        } else {
                            match PARAS.tools.run(name_id[1], &i.1) {
                                Ok(r) => r.0,
                                Err(e) => return Ok(Err(e)),
                            }
                        }
                    }
                } else if PARAS.mcp_servers.contain_server_id(name_id[1]) {
                    if !PARAS.approval_all && step_ask_approval {
                        let approval_msg = if PARAS.english {
                            format!("Do you allow calling the {} tool?{}\n{:?}", name_id[0], i.3.clone().unwrap_or_default(), i.1)
                        } else {
                            format!("是否允许调用 {} 工具？{}\n{:?}", name_id[0], i.3.clone().unwrap_or_default(), i.1)
                        };
                        if ask_approval(uuid, approval_msg, false, sender.clone()).await? {
                            match PARAS.mcp_servers.run(&name_id, &i.1).await {
                                Ok(r) => r.0,
                                Err(e) => return Ok(Err(e)),
                            }
                        } else {
                            return Err(MyError::PlanModeError{info: format!("Not allowed to call this tool: {}", name_id[0])})
                        }
                    } else {
                        match PARAS.mcp_servers.run(&name_id, &i.1).await {
                            Ok(r) => r.0,
                            Err(e) => return Ok(Err(e)),
                        }
                    }
                } else {
                    return Err(MyError::ToolNotExistError{id: name_id[1].to_string(), info: "run_tools".to_string()})
                };
                final_result = result;
                break
            }
        },
        CallToolResult::Text(test_result) => { // normal text result, not call tool
            final_result = test_result;
        },
    }
    Ok(Ok(final_result))
}

/// send message to page, insert to main message history
async fn send_and_record_message(uuid: &str, msg: String, step_num: usize, model: &str, sender: Sender<Vec<u8>>, is_diff: bool) -> Result<(), MyError> {
    // 1. send to user page
    let messages_num = get_messages_num(uuid); // 流式输出传输答案时，答案还未插入到服务端记录中，因此这里获取总消息数不需要减1
    // uuid, id, content, is_left, is_img, is_voice, is_history, is_web, time_model, current_token
    if let Err(e) = sender.send(MainData::prepare_sse(uuid, messages_num, msg.replace("\n", "srxtzn"), true, false, false, false, false, None, Some(0), None, is_diff)?).await { // 传递数据以`data: `起始，以`\n\n`终止
        event!(Level::WARN, "step {} channel send error: {:?}", step_num, e);
        return Err(MyError::PlanModeError{info: format!("step {} channel send error: {:?}", step_num, e)})
    }
    // 2. add result to main message history
    let message = ChatMessage::Assistant{
        content: Some(ChatMessageContent::Text(msg)),
        reasoning_content: None,
        refusal: None,
        name: None,
        audio: None,
        tool_calls: None,
    };
    let tmp_time = Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); // 回答的当前时间，例如：2024-10-21 16:35:47
    insert_message(uuid, message, None, tmp_time, false, DataType::Normal, None, model, None);

    // 3. page left info
    let meta_data = MetaData::new(uuid.to_string(), None);
    if let Err(e) = sender.send(meta_data.prepare_sse(&uuid)?).await { // 传递数据以`data: `起始，以`\n\n`终止
        event!(Level::WARN, "step {} channel send error: {:?}", step_num, e);
        return Err(MyError::PlanModeError{info: format!("step {} channel send error: {:?}", step_num, e)})
    }
    Ok(())
}

/// ask approval
async fn ask_approval(uuid: &str, msg: String, is_diff: bool, sender: Sender<Vec<u8>>) -> Result<bool, MyError> {
    let messages_num = get_messages_num(uuid); // 流式输出传输答案时，答案还未插入到服务端记录中，因此这里获取总消息数不需要减1
    if let Err(e) = sender.send(MainData::prepare_sse(uuid, messages_num, "".to_string(), true, false, false, false, false, None, Some(0), Some(msg.replace("\n", "srxtzn")), is_diff)?).await { // 传递数据以`data: `起始，以`\n\n`终止
        event!(Level::WARN, "ask approval error: {:?}", e);
        return Err(MyError::PlanModeError{info: format!("ask approval error: {:?}", e)})
    }
    let mut tmp_approved = false;
    update_approval(uuid, None);
    loop {
        if let Some(apv) = approved(uuid) {
            if apv {
                tmp_approved = true;
            }
            update_approval(uuid, None);
            break
        }
        // sleep 2 seconds
        sleep(Duration::from_secs(2)).await;
    }
    Ok(tmp_approved)
}

/// markdown代码块标注语言，未包含的格式后缀直接转为小写即可
const FILE_EXT_LANGUAGE: &[(&str, &str)] = &[
    ("rs", "rust"),
    ("py", "python"),
    ("js", "javascript"),
    ("ts", "typescript"),
    ("rb", "ruby"),
    ("cs", "csharp"),
    ("kt", "kotlin"),
    ("sh", "bash"),
    ("md", "markdown"),
    ("txt", "text"),
];

/// 根据文件后缀获取所属语言
fn get_file_language(file: Option<String>) -> String {
    if let Some(f) = file {
        let path = Path::new(&f);
        match path.extension() {
            Some(ext) => {
                let ext_str = ext.to_string_lossy().to_lowercase();
                FILE_EXT_LANGUAGE
                    .iter()
                    .find(|(k, _)| k == &ext_str)
                    .map(|(_, v)| v.to_string())
                    .unwrap_or_else(|| ext_str)
            }
            None => "text".to_string(),
        }
    } else {
        "text".to_string()
    }
}

/// tool名称加了`_uuid第一部分`保证不重复，但是上下文很长时可能导致模型返回的要调用的模型丢失了`_uuid第一部分`，导致调用模型失败
/// 这里把不含`_uuid第一部分`的tool名称作为key，将对应的完整tool名称作为value，存入HashMap，当`_uuid第一部分`丢失，也能确认具体要调用的模型
fn get_tool_hashmap(tools: &Vec<ChatCompletionTool>) -> HashMap<String, Vec<String>> {
    let mut out = HashMap::new();
    for t in tools {
        let real_name = t.function.name.clone();
        let name_prefix = real_name.split("__").next().unwrap().to_string();
        out.entry(name_prefix)
            .or_insert_with(Vec::new)
            .push(real_name);
    }
    out
}
