var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { AdminForthPlugin } from "adminforth";
// options:
// attachments: {
//   attachmentResource: 'description_images',
//   attachmentFieldName: 'image_path',
//   attachmentRecordIdFieldName: 'record_id',
//   attachmentResourceIdFieldName: 'resource_id',
// },
export default class RichEditorPlugin extends AdminForthPlugin {
    constructor(options) {
        super(options, import.meta.url);
        this.activationOrder = 100000;
        this.options = options;
    }
    modifyResourceConfig(adminforth, resourceConfig) {
        const _super = Object.create(null, {
            modifyResourceConfig: { get: () => super.modifyResourceConfig }
        });
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b, _c;
            _super.modifyResourceConfig.call(this, adminforth, resourceConfig);
            const c = resourceConfig.columns.find(c => c.name === this.options.htmlFieldName);
            if (!c) {
                throw new Error(`Column ${this.options.htmlFieldName} not found in resource ${resourceConfig.label}`);
            }
            if (this.options.attachments) {
                const resource = adminforth.config.resources.find(r => r.resourceId === this.options.attachments.attachmentResource);
                if (!resource) {
                    throw new Error(`Resource '${this.options.attachments.attachmentResource}' not found`);
                }
                const field = resource.columns.find(c => c.name === this.options.attachments.attachmentFieldName);
                if (!field) {
                    throw new Error(`Field '${this.options.attachments.attachmentFieldName}' not found in resource '${this.options.attachments.attachmentResource}'`);
                }
                const plugin = adminforth.activatedPlugins.find(p => p.resourceConfig.resourceId === this.options.attachments.attachmentResource &&
                    p.pluginOptions.pathColumnName === this.options.attachments.attachmentFieldName);
                if (!plugin) {
                    throw new Error(`Plugin for attachment field '${this.options.attachments.attachmentFieldName}' not found in resource '${this.options.attachments.attachmentResource}', please check if Upload Plugin is installed on the field ${this.options.attachments.attachmentFieldName}`);
                }
                this.uploadPlugin = plugin;
            }
            const filed = {
                file: this.componentPath('quillEditor.vue'),
                meta: {
                    pluginInstanceId: this.pluginInstanceId,
                    debounceTime: ((_b = (_a = this.options.completion) === null || _a === void 0 ? void 0 : _a.expert) === null || _b === void 0 ? void 0 : _b.debounceTime) || 300,
                    shouldComplete: !!this.options.completion,
                    uploadPluginInstanceId: (_c = this.uploadPlugin) === null || _c === void 0 ? void 0 : _c.pluginInstanceId,
                }
            };
            if (!c.components) {
                c.components = {};
            }
            c.components.create = filed;
            c.components.edit = filed;
        });
    }
    validateConfigAfterDiscover(adminforth, resourceConfig) {
        // optional method where you can safely check field types after database discovery was performed
        if (this.options.completion && this.options.completion.provider !== 'openai-chat-gpt') {
            throw new Error(`Invalid provider ${this.options.completion.provider}`);
        }
    }
    instanceUniqueRepresentation(pluginOptions) {
        // optional method to return unique string representation of plugin instance. 
        // Needed if plugin can have multiple instances on one resource 
        return pluginOptions.htmlFieldName;
    }
    generateRecordContext(record, maxFields, inputFieldLimit, partsCount) {
        // stringify each field
        let fields = Object.entries(record).map(([key, value]) => {
            return [key, JSON.stringify(value)];
        });
        // sort fields by length, higher first
        fields = fields.sort((a, b) => b[1].length - a[1].length);
        // select longest maxFields fields
        fields = fields.slice(0, maxFields);
        const minLimit = '...'.length * partsCount;
        if (inputFieldLimit < minLimit) {
            throw new Error(`inputFieldLimit should be at least ${minLimit}`);
        }
        // for each field, if it is longer than inputFieldLimit, truncate it using next way:
        // split into 5 parts, divide inputFieldLimit by 5, crop each part to this length, join parts using '...'
        fields = fields.map(([key, value]) => {
            if (value.length > inputFieldLimit) {
                const partLength = Math.floor(inputFieldLimit / partsCount) - '...'.length;
                const parts = [];
                for (let i = 0; i < partsCount; i++) {
                    parts.push(value.slice(i * partLength, (i + 1) * partLength));
                }
                value = parts.join('...');
                return [key, value];
            }
            return [key, value];
        });
        return JSON.stringify(Object.fromEntries(fields));
    }
    setupEndpoints(server) {
        server.endpoint({
            method: 'POST',
            path: `/plugin/${this.pluginInstanceId}/doComplete`,
            handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
                var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
                const { record } = body;
                const recordNoField = Object.assign({}, record);
                delete recordNoField[this.options.htmlFieldName];
                let currentVal = record[this.options.htmlFieldName];
                const promptLimit = ((_b = this.options.completion.expert) === null || _b === void 0 ? void 0 : _b.promptInputLimit) || 500;
                const inputContext = this.generateRecordContext(recordNoField, ((_d = (_c = this.options.completion.expert) === null || _c === void 0 ? void 0 : _c.recordContext) === null || _d === void 0 ? void 0 : _d.maxFields) || 5, ((_f = (_e = this.options.completion.expert) === null || _e === void 0 ? void 0 : _e.recordContext) === null || _f === void 0 ? void 0 : _f.maxFieldLength) || 300, ((_h = (_g = this.options.completion.expert) === null || _g === void 0 ? void 0 : _g.recordContext) === null || _h === void 0 ? void 0 : _h.splitParts) || 5);
                if (currentVal && currentVal.length > promptLimit) {
                    currentVal = currentVal.slice(-promptLimit);
                }
                const resLabel = this.resourceConfig.label;
                let content;
                if (currentVal) {
                    content = `Continue writing for text/string field "${this.options.htmlFieldName}" in the table "${resLabel}"\n` +
                        (Object.keys(recordNoField).length > 0 ? `Record has values for the context: ${inputContext}\n` : '') +
                        `Current field value: ${currentVal}\n` +
                        "Don't talk to me. Just write text. No quotes. Don't repeat current field value, just write completion\n";
                }
                else {
                    content = `Fill text/string field "${this.options.htmlFieldName}" in the table "${resLabel}"\n` +
                        (Object.keys(recordNoField).length > 0 ? `Record has values for the context: ${inputContext}\n` : '') +
                        "Be short, clear and precise. No quotes. Don't talk to me. Just write text\n";
                }
                process.env.HEAVY_DEBUG && console.log('🪲 OpenAI Prompt 🧠', content);
                const stop = ((_j = this.options.completion.expert) === null || _j === void 0 ? void 0 : _j.stop) || ['.'];
                const resp = yield fetch('https://api.openai.com/v1/chat/completions', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Authorization': `Bearer ${this.options.completion.params.apiKey}`
                    },
                    body: JSON.stringify({
                        model: this.options.completion.params.model || 'gpt-4o-mini',
                        messages: [
                            {
                                role: 'user',
                                content,
                            }
                        ],
                        temperature: ((_k = this.options.completion.expert) === null || _k === void 0 ? void 0 : _k.temperature) || 0.7,
                        max_tokens: ((_l = this.options.completion.expert) === null || _l === void 0 ? void 0 : _l.maxTokens) || 50,
                        stop,
                    })
                });
                const data = yield resp.json();
                let suggestion = data.choices[0].message.content + (data.choices[0].finish_reason === 'stop' ? (stop[0] === '.' && stop.length === 1 ? '. ' : '') : '');
                if (suggestion.startsWith(currentVal)) {
                    suggestion = suggestion.slice(currentVal.length);
                }
                const wordsList = suggestion.split(' ').map((w, i) => {
                    return (i === suggestion.split(' ').length - 1) ? w : w + ' ';
                });
                // remove quotes from start and end
                return {
                    completion: wordsList
                };
            })
        });
    }
}
