#ifndef EAIEXECUTOR_H
#define EAIEXECUTOR_H

#include "esingleton.h"
#include "eaicallbck.h"
#include "session.h"
#include "chatwindow.h"
#include "serverdefs.h"
#include "global.h"
#include "uossimplelog.h"

#include <QObject>
#include <QDBusInterface>
#include <QScopedPointer>
#include <QSharedPointer>
#include <QMap>
#include <QVariant>
#include <QDBusServiceWatcher>
#include <QVector>
#include <QJsonObject>
#include <QLocale>
#include <QTemporaryDir>
#include <QQueue>
class EAiPrompt;
class EAiExecutor : public QObject
{
    Q_OBJECT

    SINGLETONIZE_CLASS(EAiExecutor)

    explicit EAiExecutor(QObject *parent = nullptr);

public:
    enum AiAction {
        None,
        Conversation = 1, //对话模式
        DocumentSummary, //文档总结
        MaxAiAction,
    };

    enum AiTaskType {
        OverrideDocument = 1,
        ParserDocument,
        OverrideQuestion,
    };

    bool initAiSession();

    void sendRequest(const QString &llmId, uos_ai::ChatChunk &chatChunk, QObject *caller, const QString &notifier);
    QString sendWordWizardRequest(const QString &llmId, const QString &prompt, QObject *caller, const QString &notifier);

    QString sendAiRequst(const QString &llmId, QSharedPointer<EAiPrompt> prompt, QSharedPointer<EAiCallback> callback, bool isFAQGeneration = false);
    void clearAiRequest(const QObject *aiProxy);
    void cancelAiRequst(const QString &id);

    //Account manager interface
    QString currentLLMAccountId();
    QString uosAiLLMAccountId();
    QString queryLLMAccountList();
    QString queryUosAiLLMAccountList();
    bool setCurrentLLMAccountId(const QString &id);
    bool setUosAiLLMAccountId(const QString &id);

    QString currentAssistantId();
    QString queryAssistantList();
    bool setCurrentAssistantId(const QString &id);
    QString currentAssistantName();
    AssistantType currentAssistantType();

    //Config ai account
    void launchLLMConfigWindow(bool showAddllmPage = false, bool onlyUseAgreement = false, bool isFromAiQuick = false, const QString & locateTitle = "");

    void launchKnowledgeBaseConfigWindow();

    void launchAboutWindow();

    //Get Ai FAQ
    QString getRandomAiFAQ();

    //Set ChatWindow
    void setChatWindow(ChatWindow *window);
    void showToast(const QString &messge);

    void getHistoryFromChatInfo();

    /**
     * @brief 是否需要从零启动（需要等待前端渲染）
     * @return true: 从零启动; false: 只需激活窗口
     */
    bool showChatWindow();

    /**
     * @brief 给快捷面板使用，判断主窗口是不是激活状态
     * @return
     */
    bool chatWindowActive();
    //Close chat window
    void closeChatWindow();

    //Record api
    bool startRecorder(int mode);
    bool stopRecorder();
    bool isRecording();
    bool isAudioInputAvailable();
    bool isAudioOutputAvailable();

    //TTS api
    bool playTextAudio(const QString &id, const QString &text, bool isEnd);
    bool stopPlayTextAudio();

    //Sound effect
    void playSystemSound(int effId);
    //Chat history apis
    void logChatRecord(const QString &reqId,
                       const QString &question,
                       const QStringList &answer,
                       bool isRetry,
                       int err, const QString &errorInfo,
                       int actionType,
                       const QString &llmIcon,
                       const QString &llmName,
                       const QString &docSummaryParam);
    QString getChatRecords(bool lastRec);
    void clearChatRecords();

    void logConversations(const QString &assistantId, const QString &conversationId, const QString &assistantDisplayName, const QVector<uos_ai::Conversations> &convs);
    QString getConversations();
    QString createNewConversation();
    void removeConversation(const QString &assistantId, const QString &conversationId);

    QString getConversationHistoryList();
    bool setCurrentConversationId(const QString &assistantId, const QString &conversationId);
    QString getLastConversation(const QString &assistantId);

    //Image operations
    bool saveImageAs(const QString &imageData);
    bool previewImage(const QString &imageData);
    void copyImg2Clipboard(const QString &imageData);

    //Network state check
    bool isNetworkAvailable();
    bool isKnowledgeBaseExist() { return m_knowledgeBaseExist; }
    bool isEmbeddingPluginsExist() { return m_embeddingPluginsExist; }

    void personalFAQGenerate();
    void openInstallWidget(const QString &appname);

    void documentSummarySelect();
    void onDocSummaryDragInView(const QStringList &docPaths, const QString &defaultPrompt);
    void documentSummaryParsing(const QString &docPath);
    void openFile(const QString &filePath);
    void openUrl(const QString &url);

    void wordWizardContinueChat(const uos_ai::Conversations &conv);
    void appendWordWizardConv();
    void webViewLoadFinished();

    void previewRefDoc(const QString &docPath, const QStringList &docContents);

    QString getInstList();

    void rateAnwser(const int questionIdx, const int answerIdx, int rate, const QString &extJson);

    void setTitleBarMaskStatus(bool status);

    void showWarningDialog(const QString assistantId , const QString conversationId, const QString msg, bool isDelete, bool isLlmDelete);
signals:
    void llmAccountLstChanged(const QString &currentAccountId,
                              const QString &accountLst);
    void uosAiLlmAccountLstChanged();
    //ASR signal
    void audioASRInComing(const QString &text, bool isEnd);
    void audioASRError(int err, const QString &errorInfo);
    void audioInputDeviceChange(bool valid);
    void audioOutputDeviceChanged(bool valid);
    void audioSampleLevel(int level);
    //TTS signal
    void playTTSFinished(const QString &id);
    void playTTSError(int code, const QString &errorString);

    //TTP(Text to picture)
    void textToPictureFinish(const QString &id, const QStringList &paths);

    //Ai type api
    void chatConversationType(const QString &id, int action);

    //Network state changed
    void netStateChanged(bool isOnline);

    void knowledgeBaseExistChanged( bool exist);
    void knowledgeBaseFAQGenFinished();
    void localLLMStatusChanged(bool isExist);
    void embeddingPluginsStatusChanged(bool isExist);

    void sigAiReplyNone(int act, QString reply, int err);

    void docSummaryParsingStart(const QString &docPath, const QString &iconData, const QString &defaultPrompt, int error);
    void docDragInViewParserResult(int error, const QString &docPath, const QString &docContent);
    void openFileFromPathResult(bool ok);

    void sigAppendWordWizardConv();
    void sigWebViewLoadFinished();

    void previewReference(const QString &reference);

    void pptCreateSuccess(const QString &id, const QStringList &paths);
    void pptChangeSuccess(const QString &id, const QStringList &paths);

    void posterCreateSuccess(const QStringList &id, const QStringList &paths);

    void sigOverrideQues(const QString &question);

    void sigAssistantListChanged();

    void sigMainContentBackgroundColor(QString color);

    void sigGetNewHistoryList(QString currentConversationId);

    void sigHideHistoryList();

public slots:
    void onPPTCreated(const QString &id, const QList<QByteArray> &imageData);
    void onPPTChanged(const QString &id, const QList<QByteArray> &imageData);
    void onPosterCreated(const QList<QString> &idList, const QList<QByteArray> &imageData);

protected slots:
    void onCommandsReceived(const QVariantMap &commands);
    void onChatTextReceived(const QString &callId, const QString &chatText);
    void onChatError(const QString &id, int code, const QString &errorString);
    void onTextToPictureData(const QString &id, const QList<QByteArray> &imageData);

    void onAddToServerStatusChanged(const QStringList &files, int status);
    void onIndexDeleted(const QStringList &files);
    void onLocalLLMStatusChanged(bool isExist);
    void onEmbeddingPluginsStatusChanged(bool isExist);

protected:
    //Private methods
    void loadAiFAQ();

    //Init audio record
    void initAudioRecord();

    //Init network monitor
    void initNetworkMonitor();

protected:
    QSharedPointer<Session> m_aiProxy;

    //Ref to the chat window
    ChatWindow *m_chatWindow { nullptr};

    using AiCallQueue = QMap<QString, QSharedPointer<EAiCallback>>;
    AiCallQueue m_callQueue;

    //Ai temperature
    qreal m_temperature {1.0};

    const int   m_limitAQCount {6};
    QVector<QJsonObject> m_aiFAQ;
    QVector<QJsonObject> m_assistantFAQ;
    QVector<QJsonObject> m_persKnowledgeBaseFAQ;
    QString m_faqId = "";   //生成问题的ID

    QLocale m_sysLang;

    //Ai Chat history set
    struct AiConversation {
        int errCode;
        QString errInfo;
        QString reqId;
        QStringList answer;
        //LLM Info
        QString llmIcon;
        QString llmName;
    };

    struct AiChatRecord {
        QString question;
        QString questHash;
        //Conversation type
        //Ref:serverdefs.h/ChatAction
        ChatAction actType;
        QString douSummaryParam;

        QVector<AiConversation> replys;
    };

    QMap<QString, QJsonDocument> m_convsHistory;
    QVector<uos_ai::Conversations> m_currnetConvs;

    friend QDebug &operator<<(QDebug &debug, const AiChatRecord &r);

    static bool makeJsonFromChatRecord(const AiChatRecord &rec /*in*/, QJsonArray &historyArray /*out*/);

    QMap<QString, QVector<AiChatRecord>> m_assistantChatHistories;

    //Temporary directory to save TTP
    QTemporaryDir m_ttpDir;

    bool m_isAnswering = false;
    bool m_knowledgeBaseExist = false;
    bool m_embeddingPluginsExist = false;
    bool m_isFAQGenerating = false;

    // 划词助手继续对话缓存，可能会有多个继续对话
    QQueue<uos_ai::Conversations> m_wordWizardConvs;
private:
    //Make ai request context
    QString makeAiReqContext(QSharedPointer<EAiPrompt> prompt, bool isFAQGeneration = false);
    QString makeContextReq(QSharedPointer<EAiPrompt> prompt, bool isRetry);
    QString makeReq(const QString &prompt);
    void processAiRecord(const AiChatRecord &rec /*in*/, QJsonArray &ctxArray/*out*/);
    void processConversations(const uos_ai::Conversations &convs /*in*/, QJsonArray &ctxArray/*out*/);

    QString requestChatText(const QString &llmId, const QString &aiCtx, const QVariantHash &params, QSharedPointer<EAiCallback> callback); // general
    QString requestFunction(const QString &llmId, const QString &aiCtx, const QVariantHash &params, QSharedPointer<EAiCallback> callback, const QJsonArray &funcs); // functionCall
    QString chatRequest(const QString &llmId, const QString &aiCtx, const QVariantHash &params, QSharedPointer<EAiCallback> callback);  // text plain
    QString requestGenImage(const QString &llmId, const QString &aiCtx, QSharedPointer<EAiCallback> callback);
    QString searchRequest(const QString &llmId, const QString &aiCtx, QSharedPointer<EAiCallback> callback);

    QString wordWizardRequest(const QString &llmId, const QString &aiCtx, QSharedPointer<EAiCallback> callback);

    QSharedPointer<EAiPrompt> initPrompt(const QString &userQuestion, const QJsonArray &extention);
    QSharedPointer<EAiPrompt> initDefaultPrompt(const QString &userQuestion, const QJsonArray &extetion);

    QSharedPointer<EAiCallback> initCallback(QObject *caller, const QString &notifier, QSharedPointer<EAiPrompt> prompt);
    QSharedPointer<EAiCallback> initDefaultCallback(QObject *caller, const QString &notifier);

    void parseReceivedFaq(const QString &faqContent);
    void updateFaqList(const QStringList &faqList, const QString &lang, const QString &currentLang);

    bool isKnowAssistantType();

    QString appendDocContent(const QJsonArray &exts, const QString &userData);

    QString processRequest(QSharedPointer<EAiPrompt> prompt, QSharedPointer<EAiCallback> callback, const QString &llmId, const uos_ai::ChatChunk &chatChunk);

    bool isAppendConv(const uos_ai::Conversations &convs);

    void controlAssisConvsLog(const QString &assistantId, const QString &conversationId, const QString &assistantDisplayName, const QVector<uos_ai::Conversations> &convs);
    void createPPTHistory(const QString &id, const QString &displayContent);
    void createPosterHistory(const QString &idList, const QString &displayContent);
};

#define EAiExec() (ESingleton<EAiExecutor>::getInstance())
#endif // EAIEXECUTOR_H
