"use strict";

import {__} from "@wordpress/i18n";
import OpenAIClient from "openai";

export default class OpenAI {
    /**
     * API client credentials
     * @type {{baseURL: string|null, apiKey: string|null, organization: string|null, project: string|null, model: string|null}}
     */
    #credentials = {
        baseURL: null,
        apiKey: null,
        organization: null,
        project: null,
        model: null,
    };

    /**
     * API client
     *
     * @type {OpenAI|null}
     */
    #client = null;

    /**
     * Settings for requests
     *
     * @type {object}
     */
    #settings = {
        assistants: {
            list: {
                order: 'desc',
                limit: 100,
            }
        }
    };

    /**
     * Construct
     *
     * @param {string} baseURL API base URL
     * @param {string} apiKey API key
     * @param {string} organization API client organization
     * @param {string} project API client project
     * @param {string} model AI client model
     */
    constructor({baseURL, apiKey, organization, project, model}) {
        /**
         * Init properties
         */
        if (baseURL) {
            this.#credentials.baseURL = baseURL;
        }
        this.#credentials.apiKey = apiKey;
        this.#credentials.organization = organization;
        this.#credentials.project = project;
        this.#credentials.model = model;
        // Init API client
        this.#initApiClient();
    }

    /**
     * Initialize the API client
     */
    #initApiClient() {
        // Set configs to init the AI
        const config = {
            apiKey: this.#credentials.apiKey,
        };
        // Base URL
        if (this.#credentials.baseURL) {
            config.baseURL = this.#credentials.baseURL;
        }
        // Organization
        if (this.#credentials.organization) {
            config.organization = this.#credentials.organization;
        }
        // Project
        if (this.#credentials.project) {
            config.project = this.#credentials.project;
        }
        // Init API client
        this.#client = new OpenAIClient({
            ...config,
            dangerouslyAllowBrowser: true
        });
    }

    /**
     * Is the API client initialized successfully
     *
     * @returns {boolean}
     */
    isApiClientInitialized() {
        return !!this.#client;
    }

    /**
     * Generate content
     *
     * @param {string} systemInfo AI description
     * @param {string} prompt Request
     * @returns {{status: boolean,data: string}}
     */
    async generateContent({systemInfo = "You are a helpful assistant.", prompt}) {
        try {
            const result = await this.chatCompletion({systemInfo, content: prompt});
            return {
                status: result.status,
                data: result.status ? result.data?.message.content : result.data
            };
        } catch (e) {
            return {
                status: false,
                data: e.message
            };
        }
    }

    /**
     * The chat completion endpoint request
     *
     * @param {string} role Role
     * @param {string} content Content
     * @returns {Promise<ChatCompletion.Choice>|null}
     */
    async chatCompletion({systemInfo = "You are a helpful assistant.", content}) {
        try {
            if (!this.isApiClientInitialized()) {
                return {
                    status: false,
                    data: __("The OpenAI API client isn't initialized.", 'limb-chatbot')
                };
            }
            const completion = await this.#client.chat.completions.create({
                messages: [
                    {role: "system", content: systemInfo},
                    {role: "user", content},
                ],
                model: this.#credentials.model,
            });

            // TODO: track usage
            // {
            //     "choices": [...]
            //     "id":"chatcmpl-7QyqpwdfhqwajicIEznoc6Q47XAyW",
            //     "model":"gpt-4o-mini",
            //     "object":"chat.completion",
            //     "usage":{
            //         "completion_tokens":17,
            //         "prompt_tokens":57,
            //         "total_tokens":74
            //     }
            // }

            return {
                status: true,
                data: completion.choices[0]
            };
        } catch (e) {
            return {
                status: false,
                data: e.message
            };
        }
    }

    /**
     * The embedding endpoint request
     *
     * @param {string} model Model
     * @param {string} input Input
     * @returns {Promise<CreateEmbeddingResponse>}
     */
    async embedding({model = 'text-embedding-ada-002', input}) {
        try {
            const embedding = await this.#client.embeddings.create({model, input});

            return embedding;
        } catch (e) {
            console.error(e);
        }
    }

    /**
     * The image endpoint request
     *
     * @param {string} prompt Prompt
     * @returns {Promise<Array<Image>>}
     */
    async image({prompt}) {
        try {
            const image = await this.#client.images.generate({prompt});

            return image.data;
        } catch (e) {
            console.error(e);
        }
    }

    /**
     * List of assistants
     *
     * @return {Promise<{data, status: boolean}|{data: unknown, status: arg is any[]}>}
     */
    async listAssistants() {
        try {
            const assistants = await this.#client.beta.assistants.list({
                order: this.#settings.assistants.list.order,
                limit: this.#settings.assistants.list.limit,
            });
            const status = Array.isArray(assistants?.body?.data);
            return {
                status,
                data: status ? assistants.body : __("Failed to get assistants.", 'limb-chatbot'),
            }
        } catch (e) {
            return {
                status: false,
                data: e.error?.message || e.message,
            };
        }
    }

    /**
     * Create assistant
     *
     * @param {object} data Assistant data
     * @return {Promise<{data: (object|string), status: boolean}>}
     */
    async createAssistant(data) {
        try {
            const assistant = await this.#client.beta.assistants.create(data);
            const status = !!assistant?.id;
            return {
                status,
                data: status ? assistant : __("Failed to create assistant.", 'limb-chatbot'),
            }
        } catch (e) {
            return {
                status: false,
                data: e.error?.message || e.message,
            };
        }
    }

    /**
     * Get assistant
     *
     * @param {string} assistantId Assistant ID
     * @return {Promise<{data: (object|string), status: boolean}>}
     */
    async getAssistant(assistantId) {
        try {
            const res = await this.#client.beta.assistants.retrieve(assistantId);
            const status = !!res?.id;
            return {
                status,
                data: status ? res : __("Failed to get assistant.", 'limb-chatbot'),
            }
        } catch (e) {
            return {
                status: false,
                data: e.error?.message || e.message,
            };
        }
    }

    /**
     * Update assistant
     *
     * @param {number} assistantId Assistant ID
     * @param {object} data Data
     * @return {Promise<{data: (object|string), status: boolean}>}
     */
    async updateAssistant(assistantId, data) {
        try {
            const assistant = await this.#client.beta.assistants.update(assistantId, data);
            const status = !!assistant?.id;
            return {
                status,
                data: status ? assistant : __("Failed to update assistant.", 'limb-chatbot'),
            }
        } catch (e) {
            return {
                status: false,
                data: e.error?.message || e.message,
            };
        }
    }

    /**
     * Delete assistant
     *
     * @param {string} assistantId Assistant ID
     * @return {Promise<{data: (object|string), status: boolean}>}
     */
    async deleteAssistant(assistantId) {
        try {
            const res = await this.#client.beta.assistants.del(assistantId);
            const status = !!res?.deleted;
            return {
                status,
                data: status ? res : __("Failed to delete assistant.", 'limb-chatbot'),
            }
        } catch (e) {
            return {
                status: false,
                data: e.error?.message || e.message,
            };
        }
    }

    /**
     * Add file
     *
     * @param file
     * @param purpose
     * @return {Promise<{data: (object|string), status: boolean}>}
     */
    async addFile(file, purpose) {
        try {
            const newFile = await this.#client.files.create({
                file,
                purpose,
            });
            const status = !!newFile?.id;
            return {
                status,
                data: status ? newFile : __("Failed to add a file.", 'limb-chatbot'),
            }
        } catch (e) {
            return {
                status: false,
                data: e.error?.message || e.message,
            };
        }
    }

    /**
     * Get file
     *
     * @param {string} fileId File ID
     * @return {Promise<{data: (FileObject|string), status: boolean}>}
     */
    async getFile(fileId) {
        try {
            const file = await this.#client.files.retrieve(fileId);
            const status = !!file?.id;
            return {
                status,
                data: status ? file : __("Failed to get the file.", 'limb-chatbot'),
            }
        } catch (e) {
            return {
                status: false,
                data: e.error?.message || e.message,
            };
        }
    }

    /**
     * Get vector store
     *
     * @param {string} vectorStoreId Vector store ID
     * @return {Promise<{data: (VectorStore|string), status: boolean}>}
     */
    async getVectorStore(vectorStoreId) {
        try {
            const vectorStore = await this.#client.beta.vectorStores.retrieve(vectorStoreId);
            const status = !!vectorStore?.id;
            return {
                status,
                data: status ? vectorStore : __("Failed to get vector store.", 'limb-chatbot'),
            }
        } catch (e) {
            return {
                status: false,
                data: e.error?.message || e.message,
            };
        }
    }

    /**
     * Create vector store
     *
     * @param {object} data Data
     * @return {Promise<{data: (VectorStore|string), status: boolean}>}
     */
    async createVectorStore(data) {
        try {
            const vectorStore = await this.#client.beta.vectorStores.create(data);
            const status = !!vectorStore?.id;
            return {
                status,
                data: status ? vectorStore : __("Failed to create vector store.", 'limb-chatbot'),
            }
        } catch (e) {
            return {
                status: false,
                data: e.error?.message || e.message,
            };
        }
    }

    /**
     * Update vector store
     *
     * @param {string} vectorStoreId Vector store ID
     * @param {object} data Data
     * @return {Promise<{data: (VectorStore|string), status: boolean}>}
     */
    async updateVectorStore(vectorStoreId, data) {
        try {
            const vectorStore = await this.#client.beta.vectorStores.update(vectorStoreId, data);
            const status = !!vectorStore?.id;
            return {
                status,
                data: status ? vectorStore : __("Failed to update vector store.", 'limb-chatbot'),
            }
        } catch (e) {
            return {
                status: false,
                data: e.error?.message || e.message,
            };
        }
    }

    /**
     * Delete vector store
     *
     * @param {string} vectorStoreId Vector store ID
     * @return {Promise<{data: (VectorStore|string), status: boolean}>}
     */
    async deleteVectorStore(vectorStoreId) {
        try {
            const res = await this.#client.beta.vectorStores.del(vectorStoreId, data);
            const status = !!res?.deleted;
            return {
                status,
                data: status ? res : __("Failed to update vector store.", 'limb-chatbot'),
            }
        } catch (e) {
            return {
                status: false,
                data: e.error?.message || e.message,
            };
        }
    }

    /**
     * Get vector store files
     *
     * @param {string} vectorStoreId Vector store ID
     * @param {integer} limit Limit
     * @param {string} order Order: asc/desc
     * @param {string} after After file ID
     * @param {string} before After file ID
     * @param {string} filter Filter by file status: in_progress/completed/failed/cancelled
     * @return {Promise<{data: (VectorStoreFilesPage|string), status: boolean}>}
     */
    async getVectorStoreFiles(vectorStoreId, {limit, order, after, before, filter}) {
        try {
            // Query params
            const query = {};
            limit && (query.limit = limit);
            order && (query.order = order);
            after && (query.after = after);
            before && (query.before = before);
            filter && (query.filter = filter);
            // Get files
            const res = await this.#client.beta.vectorStores.files.list(vectorStoreId, query);
            const status = res?.body?.object === 'list';
            return {
                status,
                data: status ? res.body : __("Failed to get vector store files.", 'limb-chatbot'),
            }
        } catch (e) {
            return {
                status: false,
                data: e.error?.message || e.message,
            };
        }
    }

    /**
     * Add files to vector store
     *
     * @param {string} vectorStoreId Vector store ID
     * @param {string[]} filesIds Files IDs
     * @return {Promise<{data: object|string, status: boolean}>}
     */
    async addFilesToVectorStore(vectorStoreId, filesIds) {
        try {
            const batch = await this.#client.beta.vectorStores.fileBatches.create(vectorStoreId, {
                file_ids: filesIds,
            });
            const status = !!batch?.id;
            return {
                status,
                data: status ? batch : __("Failed to add files to vector store.", 'limb-chatbot'),
            }
        } catch (e) {
            return {
                status: false,
                data: e.error?.message || e.message,
            };
        }
    }

    /**
     * Delete vector store file
     *
     * @param {string} vectorStoreId Vector store ID
     * @param {string} fileId File ID
     * @return {Promise<{data: (VectorStoreFileDeleted|string), status: boolean}>}
     */
    async deleteFileFromVectorStore(vectorStoreId, fileId) {
        try {
            const res = await this.#client.beta.vectorStores.files.del(vectorStoreId, fileId);
            const status = !!res?.deleted;
            return {
                status,
                data: status ? res : __("Failed to delete vector store file.", 'limb-chatbot'),
            }
        } catch (e) {
            return {
                status: false,
                data: e.error?.message || e.message,
            };
        }
    }
}
