/**
 * 扣子智能体
 */
import {
    CozeAPI,
    COZE_CN_BASE_URL,
    ChatEventType,
    RoleType,
    StreamChatReq,
    EnterMessage,
    // ChatWorkflowReq,
    // CreateChatReq,
    // ChatStatus,
    VariableUpdateReq,
    VariableRetrieveReq,
    CreateChatReq,
    RunWorkflowReq,
    RunWorkflowData,
    CreateChatData,
    ChatStatus,
    WorkflowEventType,
    ChatWorkflowReq
} from '@coze/api';
import GptBase from "./gptbase"
import { CorzAuthorization } from './corzauthorization';
import { parse } from 'querystring'
import { ApiResult } from './declare';

// 定义深度思考的动作标签
// 这是小鹭里面专用的几个思考动作标签
const DeepThinkingAction: any = {
    thinking: { start: '<xiaolu-think>', end: '</xiaolu-think>', tag: 'xiaolu-think' },
    reasoning: { start: '<xiaolu-reason>', end: '</xiaolu-reason>', tag: 'xiaolu-reason' },
    card: { start: '<xiaolu-card>', end: '</xiaolu-card>', tag: 'xiaolu-card' },
}
// 定义深度思考的状态
enum DeepThinkingStatus {
    None,
    Thinking,
    // ContentOutput,
    ThinkingOver
}
// 智能体思考时输出的动作
type CardType = { type: number, tag: string }
// type TThinkingMessage = { action: string, textposition: number }
export default class CorzBot extends GptBase {
    private botid: string | null = null;         // 智能体id
    private workflowid: string | null = null;    // 工作流id
    private talkflowid: string | null = null;   // 对话流id
    private apiKey: string;
    /**
     * 
     * @param apikey 调用AI中台 的key
     * @param botid  智能体信息 
     */
    constructor(private authorizationProvider: CorzAuthorization, private setting: any = {}) {
        super();
        ////初始化扣子客户端
        
        if (this.setting.workflowid) 
            this.workflowid = this.setting.workflowid;
        else if (this.setting.talkflowid)
            this.talkflowid = this.setting.talkflowid;
        else if (this.setting.botid || this.setting.botID)
            this.botid = this.setting.botid || this.setting.botID;
        else
            throw new Error('no botid or talkflowid or workflowid setting for coze');
        this.apiKey = this.setting['apiKey'];
    }
    private async createClient(): Promise<CozeAPI> {
        // 存在AppKey则使用AppKey进行认证
        if (this.apiKey) return new CozeAPI({ baseURL: COZE_CN_BASE_URL, token: this.apiKey });
        const accessToken = await this.authorizationProvider.getAccessToken();
        if (!accessToken) throw new Error('get access token failed');
        return new CozeAPI({ baseURL: COZE_CN_BASE_URL, token: accessToken });
    }
    /**
     * 发起一次会话
     */
    override async createCoversation(client?: CozeAPI): Promise<string | null> {
        try {
            const czApi = client ?? await this.createClient();
            const params = this.botid ? { bot_id: this.botid } : {};
            const result = await czApi.conversations.create(params)
            return result.id;
        } catch (error) {
            console.error('createCoversation error in coze api');
            return null;
        }
    }
    /**
     * 设置Coze的变量
     * @param params 
     * @returns 
     */
    override async setVariables(params: any): Promise<ApiResult> {
        const client = await this.createClient();
        const cozeParams: VariableUpdateReq = Object.assign({}, this.botid ? { bot_id: this.botid } : {}, params);
        client.variables.update(cozeParams)
        return { successed: true };
    }
    /**
     * 获取设置的变量
     * @returns 
     */
    override async getVariables(params: any): Promise<any> {
        const client = await this.createClient();
        const cozeParams: VariableRetrieveReq = Object.assign({}, { bot_id: this.botid }, params);
        const data = await client.variables.retrieve(cozeParams);
        if (data) return { successed: true, ...data }
        return { successed: false, error: 'get variables failed' };
    }
    /**
     * 组装请求参数
     * @param message 
     * @param callChatOption 
     * @param attach 
     * @param _axiosOption 
     */
    async getRequestStream<T>(client: CozeAPI, message: EnterMessage[], callChatOption: any = {}): Promise<T> {
        //简单的对话请求
        const conversation_id = callChatOption.session_id ?? await this.createCoversation(client);
        if (this.botid) {
            const req: StreamChatReq = {
                bot_id: this.botid,
                additional_messages: message,
                user_id: callChatOption.userid || callChatOption.cozeUserID,
                conversation_id,
                parameters: Object.assign({ request_src:1 }, callChatOption.parameters || {}),
            }
            req.custom_variables = Object.assign({}, this.setting.customVariables || {}, callChatOption.customVariables || {});
            return req as T;
        }
        if (this.workflowid){
            const worflowreq: RunWorkflowReq = {
                ext: callChatOption.ext,
                workflow_id: this.workflowid,//callChatOption.workflowid,
                is_async: false,
                // parameters: Object.assign({ input: message[0]?.content }, this.setting.customVariables || {}, callChatOption.customVariables || {}),
                parameters: Object.assign({ request_src :1},callChatOption.parameters || {}, { input: message[0]?.content})
            }
            return worflowreq as T;
        }
        const worflowreq: ChatWorkflowReq = {
            additional_messages: message,
            ext: callChatOption.ext,
            workflow_id: this.talkflowid!,//callChatOption.workflowid,
            conversation_id,
            parameters: Object.assign({request_src:1},callChatOption.parameters || {}) //Object.assign({}, this.setting.customVariables || {}, callChatOption.customVariables || {}),
            //parameters: { input: message[0]?.content }
        }
        return worflowreq as T;
    }
    /**
     * 同步调用的问答请求需要获取会话详情
     * @param chatid 
     * @param conversation_id 
     */
    private async getChatDetail(client: CozeAPI, conversation_id: string, chatid: string) {
        const maxWaitTime = 120000; // 最大等待120秒
        const startTime = Date.now();
        while ((Date.now() - startTime) < maxWaitTime) {
            const chatinfo = await client.chat.retrieve(conversation_id, chatid);
            // 状态已经是完成了，可以去获取对话的内容了
            if (chatinfo.status === ChatStatus.COMPLETED) return { usage: chatinfo.usage, messages: await client.chat.messages.list(conversation_id, chatid) };
            await new Promise(resolve => setTimeout(resolve, 1500)); // 等待1500ms
        }
        return null;
    }
    /**
     * 非流式传输聊天请求
     * @param chatText 
     * @param callChatOption 
     * @param _axiosOption 
     */
    public async chatRequest(message: any[] | string, callChatOption: any = {}, _axiosOption: any = {}): Promise<any> {
        if (!message) this.emit('chaterror', { successed: false, error: 'no message in chat' });
        ///如果是字符，则组装成API需要的消息
        if (typeof message === 'string') message = [
            {
                role: RoleType.User,
                content_type: 'text',
                content: message
            }
        ];
        const client = await this.createClient();

        ////如果参数中用的是Workflow，则调用对话流来输出结果
        ////否则用智能体的对话来输出结果
        const params = await this.getRequestStream(client, message, callChatOption);
        const response = this.botid ? await client.chat.create(params as CreateChatReq) :await client.workflows.runs.create(params as RunWorkflowReq);
        if (this.workflowid && (response as RunWorkflowData).msg === 'Success') {
            const resp = (response as RunWorkflowData).data;
            return { successed: true, message: [{ role: 'assistant', type: 'answer', content: resp }] };
            // try {
            //     return { successed: true, message: [{ role: 'assistant', type: 'answer', content: JSON.parse(resp).data }] };
            // } catch (error) {
            //     return { successed: true, message: [{ role: 'assistant', type: 'answer', content: resp }] };
            // }
        }
        if (!this.workflowid && (response as CreateChatData).conversation_id && (response as CreateChatData).id) {
            const ccd = response as CreateChatData;
            const chatData = await this.getChatDetail(client, ccd.conversation_id, ccd.id);
            if (chatData) {
                const message = chatData.messages.filter(x => x.type === 'answer').map(item => ({
                    role: item.role,
                    type: item.type,
                    content: item.content,
                }));
                return { successed: true, message, usage: chatData.usage, session_id: ccd.conversation_id };
            }
        }
        return { successed: false, error: { message: '聊天未完成' } };

    }
    /**
     * 提取XML标签中间的内容，
     * @param xmlStr 
     * @param tagName 
     * @returns 
     */
    extractXmlContent(xmlStr: string, tagName: string): string {
        // 构建匹配标签的正则（支持任意空白字符和大小写）
        const regex = new RegExp(`<\\s*${tagName}\\s*>([\\s\\S]*?)<\\s*\\/\\s*${tagName}\\s*>`, 'i');
        const match = xmlStr.match(regex);
        if (match && match[1]) return match[1].trim();
        return ''; // 未找到标签或内容
    }
    /**
     * 解析深度思考的JSON内容
     * @param content 
     * @param status 
     * @returns 
     */
    private parseDeepThinkingJson(content: string) {
        // const thinkingStartIndex = status === DeepThinkingStatus.Thinking ? content.indexOf("{\"process_msg") : 
        //                            (status===DeepThinkingStatus.ReasonOutput ? content.indexOf("{\"reasoning_content") : content.indexOf("{\"card_resource")) ;
        const xmlTagLocation = [content.indexOf(DeepThinkingAction.thinking.start),
        content.indexOf(DeepThinkingAction.reasoning.start),
        content.indexOf(DeepThinkingAction.card.start)];
        let minLocation = 10000, minIndex = -1;
        // 找到最小的并且大于0 的位置的下标
        xmlTagLocation.forEach((x, index) => {
            if (x >= 0 && x < minLocation) {
                minLocation = x;
                minIndex = index;
            }
        });
        // const tagLocation = xmlTagLocation.filter(x => x >= 0);
        const thinkingStartIndex = minIndex >= 0 ? minLocation : -1; //tagLocation.length > 0 ? Math.min(...tagLocation) : -1;
        if (thinkingStartIndex < 0) return { content, status: DeepThinkingStatus.ThinkingOver };
        const currentAction = [DeepThinkingAction.thinking, DeepThinkingAction.reasoning, DeepThinkingAction.card][minIndex];
        const currentActionIsOver = content.indexOf(currentAction.end, thinkingStartIndex);
        const thinkingEndIndex = currentActionIsOver >= 0 ? currentActionIsOver: content.indexOf('</', thinkingStartIndex);
        const thinkingContent = this.extractXmlContent(content.substring(thinkingStartIndex, thinkingEndIndex >= 0 ? thinkingEndIndex : undefined) + currentAction.end, currentAction.tag);  //"\"}";
        if (currentActionIsOver >= 0) content = content.substring(currentActionIsOver + currentAction.end.length);
        return { 
            thinking: {
                action: currentAction,
                text: thinkingContent,//Object.values(thinkingObject)[0]
                completed: currentActionIsOver >= 0
            }, content};
       
    }
    /**
     * 流式传输聊天请求
     * @param chatText 
     * @param callChatOption 
     * @param attach 
     * @param _axiosOption 
     * @returns 
     */
    override async chatRequestInStream(message: any[] | string, callChatOption: any = {}, attach?: any, _axiosOption?: any): Promise<any> {
        if (!message) this.emit('chaterror', { successed: false, error: 'no message in chat' });
        ///如果是字符，则组装成API需要的消息
        if (typeof message === 'string') message = [
            {
                role: RoleType.User,
                content_type: 'text',
                content: message
            }
        ];
        const client = await this.createClient();
        ////如果参数中用的是Workflow，则调用对话流来输出结果
        ////否则用智能体的对话来输出结果
        let requestid = Math.ceil(Math.random() * (new Date().getTime() * Math.random()) / 1000), index = 0;
        const params = await this.getRequestStream(client, message, callChatOption);
        const stream = this.botid ? client.chat.stream(params as StreamChatReq) : 
            (this.workflowid ? client.workflows.runs.stream(params as RunWorkflowReq) : 
            client.workflows.chat.stream(params as ChatWorkflowReq)) ;
        let deltaindex = 0, fullanswer: string[] = [], followup: string[] = [], cardData: CardType[] = [];
        let deepThinking = '', thinkingStatus = DeepThinkingStatus.None, cardResource = ''; // 是否在深度思考中
        for await (const part of stream) {
            if (part.event === ChatEventType.ERROR) return this.emit('chaterror', { successed: false, error: 'call failed' });
            if (part.event === ChatEventType.CONVERSATION_MESSAGE_DELTA ||
                part.event === WorkflowEventType.MESSAGE
            ) {
                let { conversation_id, content_type, type = 'answer', role = 'assistant', content, reasoning_content: reasoning } = part.data as any;
                if (!content&& reasoning) {
                    this.emit('chatthinking', { text: reasoning, completed: false, action: 'deep-thinking' });
                    continue;
                }
                // 如果存在深度思考，则一开始就会带有相关的关键信息
                if (deltaindex === 0 && content.startsWith("<xiaolu-")) {
                    thinkingStatus = DeepThinkingStatus.Thinking;
                    deltaindex++;
                }
                // 如果在深度思考中，则不输出消息
                if (thinkingStatus === DeepThinkingStatus.Thinking) {
                    deepThinking += content;
                    const result = this.parseDeepThinkingJson(deepThinking)
                    const thinking = result.thinking;
                    if (thinking) {
                        deepThinking = result.content;
                        if (thinking.action === DeepThinkingAction.card) {
                            cardResource += result.thinking.text;
                            // 卡片流结束，解析卡片资源数据
                            if (result.thinking.completed) {
                                const allCards = cardResource.replace(/[\x00-\x1F\x7F]/g, '').split('|')
                                for (const item of allCards) {
                                    const cardinfo: any = parse(item);
                                    if (cardinfo.type && cardinfo.tag) cardData.push({ ...cardinfo })
                                }
                                // 将卡片资源返回给客户端
                                this.emit('chatcard', cardData);
                            }
                        } else {
                            this.emit('chatthinking', { text: result.thinking.text, completed: result.thinking.completed, action: thinking.action?.tag });
                        }
                    }
                    if (result.status != DeepThinkingStatus.ThinkingOver) continue;
                    thinkingStatus = DeepThinkingStatus.ThinkingOver;
                    // 将排除了thinking之后的消息内容要带下去成为正式的输出内容
                    content = deepThinking;
                }
                fullanswer.push(content);
                let output = { successed: true, type, content_type, role, requestid, segment: content, text: fullanswer.join(''), index: index++, session_id: conversation_id };
                if (attach) output = Object.assign({}, output, attach);
                this.emit('chattext', output)
            }
            ////在流式传输中，提取相关推荐问题
            if (part.event === ChatEventType.CONVERSATION_MESSAGE_COMPLETED) {
                const { type, content } = part.data ?? {};
                if (type === 'follow_up') followup.push(content);
            }
            ///整个对话结束
            if (part.event === ChatEventType.CONVERSATION_CHAT_COMPLETED ||
                part.event === WorkflowEventType.DONE
            ) {
                const { conversation_id, content } =( part.data )?? {} as any;
                let output = { successed: true, cards: cardData.length ? cardData : null, followup, type: 'answer', content_type: 'text', role: RoleType.Assistant, requestid, segment: null, text: content??fullanswer.join(''), index: index++, session_id: conversation_id };
                if (attach) output = Object.assign({}, output, attach);
                this.emit('chatdone', output)
            }
        }
        return { successed: true, requestid }
    }
}