import TabAwareWidget from "./tab_aware_widget.js";
import libraryLoader from "../services/library_loader.js";
import noteAutocompleteService from "../services/note_autocomplete.js";
import treeCache from "../services/tree_cache.js";
import server from "../services/server.js";
import ws from "../services/ws.js";
import SpacedUpdate from "../services/spaced_update.js";
import attributesParser from "../services/attribute_parser.js";
import AttributeDetailWidget from "./attribute_detail.js";
import contextMenuService from "../services/context_menu.js";
const mentionSetup = {
    feeds: [
        {
            marker: '@',
            feed: queryText => {
                return new Promise((res, rej) => {
                    noteAutocompleteService.autocompleteSource(queryText, rows => {
                        res(rows.map(row => {
                            return {
                                id: '@' + row.notePathTitle,
                                name: row.notePathTitle,
                                link: '#' + row.notePath,
                                notePath: row.notePath,
                                highlightedNotePathTitle: row.highlightedNotePathTitle
                            }
                        }));
                    });
                });
            },
            itemRenderer: item => {
                const itemElement = document.createElement('span');
                itemElement.classList.add('mentions-item');
                itemElement.innerHTML = `${item.highlightedNotePathTitle} `;
                return itemElement;
            },
            minimumCharacters: 0
        },
        {
            marker: '#',
            feed: async queryText => {
                const names = await server.get(`attributes/names/?type=label&query=${encodeURIComponent(queryText)}`);
                return names.map(name => {
                    return {
                        id: '#' + name,
                        name: name
                    }
                });
            },
            minimumCharacters: 0,
            attributeMention: true
        },
        {
            marker: '~',
            feed: async queryText => {
                const names = await server.get(`attributes/names/?type=relation&query=${encodeURIComponent(queryText)}`);
                return names.map(name => {
                    return {
                        id: '~' + name,
                        name: name
                    }
                });
            },
            minimumCharacters: 0,
            attributeMention: true
        }
    ]
};
const editorConfig = {
    removePlugins: [
        'Enter',
        'ShiftEnter',
        'Heading',
        'Link',
        'Autoformat',
        'Bold',
        'Italic',
        'Underline',
        'Strikethrough',
        'Code',
        'Superscript',
        'Subscript',
        'BlockQuote',
        'Image',
        'ImageCaption',
        'ImageStyle',
        'ImageToolbar',
        'ImageUpload',
        'ImageResize',
        'List',
        'TodoList',
        'PasteFromOffice',
        'Table',
        'TableToolbar',
        'TableProperties',
        'TableCellProperties',
        'Indent',
        'IndentBlock',
        'BlockToolbar',
        'ParagraphButtonUI',
        'HeadingButtonsUI',
        'UploadimagePlugin',
        'InternalLinkPlugin',
        'MarkdownImportPlugin',
        'CuttonotePlugin',
        'TextTransformation',
        'Font',
        'FontColor',
        'FontBackgroundColor',
        'CodeBlock',
        'SelectAll',
        'IncludeNote',
        'CutToNote'
    ],
    toolbar: {
        items: []
    },
    placeholder: "Type the labels and relations here ...",
    mention: mentionSetup
};
const TPL = `
`;
export default class NoteAttributesWidget extends TabAwareWidget {
    constructor() {
        super();
        this.attributeDetailWidget = new AttributeDetailWidget().setParent(this);
        this.spacedUpdate = new SpacedUpdate(() => {
            this.parseAttributes();
            this.attributeDetailWidget.hide();
        });
    }
    doRender() {
        this.$widget = $(TPL);
        this.$editor = this.$widget.find('.note-attributes-editor');
        this.initialized = this.initEditor();
        this.$attrDisplay = this.$widget.find('.attr-display');
        this.$ownedExpander = this.$widget.find('.attr-owned-expander');
        this.$ownedExpander.on('click', () => {
            if (this.$attrDisplay.is(":visible")) {
                this.$attrDisplay.slideUp(200);
            }
            else {
                this.$attrDisplay.slideDown(200);
            }
        });
        this.$ownedExpanderText = this.$ownedExpander.find('.attr-expander-text');
        this.$inheritedAttributes = this.$widget.find('.inherited-attributes');
        this.$inheritedExpander = this.$widget.find('.attr-inherited-expander');
        this.$inheritedExpander.on('click', () => {
            if (this.$inheritedAttributes.is(":visible")) {
                this.$inheritedAttributes.slideUp(200);
            }
            else {
                this.$inheritedAttributes.slideDown(200);
            }
        });
        this.$inheritedExpanderText = this.$inheritedExpander.find('.attr-expander-text');
        this.$inheritedEmptyExpander = this.$widget.find('.attr-inherited-empty-expander');
        this.$editor.on('keydown', async e => {
            const keycode = (e.keyCode ? e.keyCode : e.which);
            if (keycode === 13) {
                this.triggerCommand('focusOnDetail', {tabId: this.tabContext.tabId});
                await this.save();
            }
            this.attributeDetailWidget.hide();
        });
        this.$addNewAttributeButton = this.$widget.find('.add-new-attribute-button');
        this.$addNewAttributeButton.on('click', e => {
            contextMenuService.show({
                x: e.pageX,
                y: e.pageY,
                items: [
                    {title: "Add new label", command: "addNewLabel", uiIcon: "hash"},
                    {title: "Add new relation", command: "addNewRelation", uiIcon: "transfer"},
                    {title: "----"},
                    {title: "Add new label definition", command: "addNewRelation", uiIcon: "empty"},
                    {title: "Add new relation definition", command: "addNewRelation", uiIcon: "empty"},
                ],
                selectMenuItemHandler: ({command}) => {
                    console.log(command);
                }
            });
        });
        this.$widget.append(this.attributeDetailWidget.render());
    }
    async save() {
        const attributes = this.parseAttributes();
        if (attributes) {
            await server.put(`notes/${this.noteId}/attributes`, attributes, this.componentId);
        }
    }
    parseAttributes() {
        try {
            const attrs = attributesParser.lexAndParse(this.textEditor.getData());
            this.$widget.removeClass("error");
            this.$widget.removeAttr("title");
            this.$ownedExpander.removeClass("error");
            this.$ownedExpanderText.text(attrs.length + ' owned ' + this.attrPlural(attrs.length));
            return attrs;
        }
        catch (e) {
            this.$widget.attr("title", e.message);
            this.$widget.addClass("error");
            this.$ownedExpander.addClass("error");
            this.$ownedExpanderText.text(e.message);
        }
    }
    async initEditor() {
        await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
        this.$widget.show();
        this.$editor.on("click", e => this.handleEditorClick(e));
        this.textEditor = await BalloonEditor.create(this.$editor[0], editorConfig);
        this.textEditor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate());
        // disable spellcheck for attribute editor
        this.textEditor.editing.view.change(writer => writer.setAttribute('spellcheck', 'false', this.textEditor.editing.view.document.getRoot()));
        //await import(/* webpackIgnore: true */'../../libraries/ckeditor/inspector.js');
        //CKEditorInspector.attach(this.textEditor);
    }
    async handleEditorClick(e) {
        const pos = this.textEditor.model.document.selection.getFirstPosition();
        if (pos && pos.textNode && pos.textNode.data) {
            const clickIndex = this.getClickIndex(pos);
            const parsedAttrs = attributesParser.lexAndParse(this.textEditor.getData(), true);
            let matchedAttr = null;
            for (const attr of parsedAttrs) {
                if (clickIndex >= attr.startIndex && clickIndex <= attr.endIndex) {
                    matchedAttr = attr;
                    break;
                }
            }
            this.attributeDetailWidget.showAttributeDetail({
                allAttributes: parsedAttrs,
                attribute: matchedAttr,
                isOwned: true,
                x: e.pageX,
                y: e.pageY
            });
        }
    }
    getClickIndex(pos) {
        let clickIndex = pos.offset - pos.textNode.startOffset;
        let curNode = pos.textNode;
        while (curNode.previousSibling) {
            curNode = curNode.previousSibling;
            if (curNode.name === 'reference') {
                clickIndex += curNode._attrs.get('notePath').length + 1;
            } else {
                clickIndex += curNode.data.length;
            }
        }
        return clickIndex;
    }
    async loadReferenceLinkTitle(noteId, $el) {
        const note = await treeCache.getNote(noteId, true);
        let title;
        if (!note) {
            title = '[missing]';
        }
        else if (!note.isDeleted) {
            title = note.title;
        }
        else {
            title = note.isErased ? '[erased]' : `${note.title} (deleted)`;
        }
        $el.text(title);
    }
    async refreshWithNote(note) {
        await this.renderOwnedAttributes(note.getOwnedAttributes());
        const inheritedAttributes = note.getAttributes().filter(attr => attr.noteId !== this.noteId);
        if (inheritedAttributes.length === 0) {
            this.$inheritedExpander.hide();
            this.$inheritedEmptyExpander.show();
        }
        else {
            this.$inheritedExpander.show();
            this.$inheritedEmptyExpander.hide();
        }
        this.$inheritedExpanderText.text(inheritedAttributes.length + ' inherited ' + this.attrPlural(inheritedAttributes.length));
        this.$inheritedAttributes.empty();
        await this.renderAttributesIntoDiv(inheritedAttributes, this.$inheritedAttributes);
        this.parseAttributes();
    }
    async renderOwnedAttributes(ownedAttributes) {
        const $attributesContainer = $("");
        await this.renderAttributesIntoCKEditor(ownedAttributes, $attributesContainer);
        await this.spacedUpdate.allowUpdateWithoutChange(() => {
            this.textEditor.setData($attributesContainer.html());
        });
    }
    attrPlural(number) {
        return 'attribute' + (number === 1 ? '' : 's');
    }
    createNoteLink(noteId) {
        return $("
", {
            href: '#' + noteId,
            class: 'reference-link',
            'data-note-path': noteId
        });
    }
    async renderAttributesIntoCKEditor(attributes, $container) {
        for (const attribute of attributes) {
            this.renderAttribute(attribute, $container);
        }
    }
    renderAttributesIntoDiv(attributes, $container) {
        for (const attribute of attributes) {
            const $span = $("")
                .on('click', e => this.attributeDetailWidget.showAttributeDetail({
                    attribute: {
                        noteId: attribute.noteId,
                        type: attribute.type,
                        name: attribute.name,
                        value: attribute.value
                    },
                    isOwned: false,
                    x: e.pageX,
                    y: e.pageY
                }));
            $container.append($span);
            this.renderAttribute(attribute, $span);
        }
    }
    renderAttribute(attribute, $container) {
        if (attribute.type === 'label') {
            $container.append(document.createTextNode('#' + attribute.name));
            if (attribute.value) {
                $container.append('=');
                $container.append(document.createTextNode(this.formatValue(attribute.value)));
            }
            $container.append(' ');
        } else if (attribute.type === 'relation') {
            if (attribute.isAutoLink) {
                return;
            }
            if (attribute.value) {
                $container.append(document.createTextNode('~' + attribute.name + "="));
                $container.append(this.createNoteLink(attribute.value));
                $container.append(" ");
            } else {
                ws.logError(`Relation ${attribute.attributeId} has empty target`);
            }
        } else {
            ws.logError("Unknown attr type: " + attribute.type);
        }
    }
    formatValue(val) {
        if (/^[\p{L}\p{N}\-_,.]+$/u.test(val)) {
            return val;
        }
        else if (!val.includes('"')) {
            return '"' + val + '"';
        }
        else if (!val.includes("'")) {
            return "'" + val + "'";
        }
        else if (!val.includes("`")) {
            return "`" + val + "`";
        }
        else {
            return '"' + val.replace(/"/g, '\\"') + '"';
        }
    }
    async focusOnAttributesEvent({tabId}) {
        if (this.tabContext.tabId === tabId) {
            this.$editor.trigger('focus');
        }
    }
    updateAttributeListCommand({attributes}) {
        this.renderOwnedAttributes(attributes);
    }
}