import { t } from "../../services/i18n.js";
import server from "../../services/server.js";
import froca from "../../services/froca.js";
import linkService from "../../services/link.js";
import attributeAutocompleteService from "../../services/attribute_autocomplete.js";
import noteAutocompleteService from "../../services/note_autocomplete.js";
import promotedAttributeDefinitionParser from '../../services/promoted_attribute_definition_parser.js';
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import SpacedUpdate from "../../services/spaced_update.js";
import utils from "../../services/utils.js";
import shortcutService from "../../services/shortcuts.js";
import appContext from "../../components/app_context.js";
const TPL = `
    
    
        
${t('attribute_detail.attr_detail_title')} 
        
        ${t('attribute_detail.attr_is_owned_by')}
    
    
        
            ${t('attribute_detail.save_and_close')} 
            
        
            ${t('attribute_detail.delete')} 
    
    
 `;
const DISPLAYED_NOTES = 10;
const ATTR_TITLES = {
    "label": t('attribute_detail.label'),
    "label-definition": t('attribute_detail.label_definition'),
    "relation": t('attribute_detail.relation'),
    "relation-definition": t('attribute_detail.relation_definition')
};
const ATTR_HELP = {
    "label": {
        "disableVersioning": t('attribute_detail.disable_versioning'),
        "calendarRoot": t('attribute_detail.calendar_root'),
        "archived": t('attribute_detail.archived'),
        "excludeFromExport": t('attribute_detail.exclude_from_export'),
        "run": t('attribute_detail.run'),
        "runOnInstance": t('attribute_detail.run_on_instance'),
        "runAtHour": t('attribute_detail.run_at_hour'),
        "disableInclusion": t('attribute_detail.disable_inclusion'),
        "sorted": t('attribute_detail.sorted'),
        "sortDirection": t('attribute_detail.sort_direction'),
        "sortFoldersFirst": t('attribute_detail.sort_folders_first'),
        "top": t('attribute_detail.top'),
        "hidePromotedAttributes": t('attribute_detail.hide_promoted_attributes'),
        "readOnly": t('attribute_detail.read_only'),
        "autoReadOnlyDisabled": t('attribute_detail.auto_read_only_disabled'),
        "appCss": t('attribute_detail.app_css'),
        "appTheme": t('attribute_detail.app_theme'),
        "cssClass": t('attribute_detail.css_class'),
        "iconClass": t('attribute_detail.icon_class'),
        "pageSize": t('attribute_detail.page_size'),
        "customRequestHandler": t('attribute_detail.custom_request_handler'),
        "customResourceProvider": t('attribute_detail.custom_resource_provider'),
        "widget": t('attribute_detail.widget'),
        "workspace": t('attribute_detail.workspace'),
        "workspaceIconClass": t('attribute_detail.workspace_icon_class'),
        "workspaceTabBackgroundColor": t('attribute_detail.workspace_tab_background_color'),
        "workspaceCalendarRoot": t('attribute_detail.workspace_calendar_root'),
        "workspaceTemplate": t('attribute_detail.workspace_template'),
        "searchHome": t('attribute_detail.search_home'),
        "workspaceSearchHome": t('attribute_detail.workspace_search_home'),
        "inbox": t('attribute_detail.inbox'),
        "workspaceInbox": t('attribute_detail.workspace_inbox'),
        "sqlConsoleHome": t('attribute_detail.sql_console_home'),
        "bookmarkFolder": t('attribute_detail.bookmark_folder'),
        "shareHiddenFromTree": t('attribute_detail.share_hidden_from_tree'),
        "shareExternalLink": t('attribute_detail.share_external_link'),
        "shareAlias": t('attribute_detail.share_alias'),
        "shareOmitDefaultCss": t('attribute_detail.share_omit_default_css'),
        "shareRoot": t('attribute_detail.share_root'),
        "shareDescription": t('attribute_detail.share_description'),
        "shareRaw": t('attribute_detail.share_raw'),
        "shareDisallowRobotIndexing": t('attribute_detail.share_disallow_robot_indexing'),
        "shareCredentials": t('attribute_detail.share_credentials'),
        "shareIndex": t('attribute_detail.share_index'),
        "displayRelations": t('attribute_detail.display_relations'),
        "hideRelations": t('attribute_detail.hide_relations'),
        "titleTemplate": t('attribute_detail.title_template'),
        "template": t('attribute_detail.template'),
        "toc": t('attribute_detail.toc'),
        "color": t('attribute_detail.color'),
        "keyboardShortcut": t('attribute_detail.keyboard_shortcut'),
        "keepCurrentHoisting": t('attribute_detail.keep_current_hoisting'),
        "executeButton": t('attribute_detail.execute_button'),
        "executeDescription": t('attribute_detail.execute_description'),
        "excludeFromNoteMap": t('attribute_detail.exclude_from_note_map'),
        "newNotesOnTop": t('attribute_detail.new_notes_on_top'),
        "hideHighlightWidget": t('attribute_detail.hide_highlight_widget')
    },
    "relation": {
        "runOnNoteCreation": t('attribute_detail.run_on_note_creation'),
        "runOnChildNoteCreation": t('attribute_detail.run_on_child_note_creation'),
        "runOnNoteTitleChange": t('attribute_detail.run_on_note_title_change'),
        "runOnNoteContentChange": t('attribute_detail.run_on_note_content_change'),
        "runOnNoteChange": t('attribute_detail.run_on_note_change'),
        "runOnNoteDeletion": t('attribute_detail.run_on_note_deletion'),
        "runOnBranchCreation": t('attribute_detail.run_on_branch_creation'),
        "runOnBranchChange": t('attribute_detail.run_on_branch_change'),
        "runOnBranchDeletion": t('attribute_detail.run_on_branch_deletion'),
        "runOnAttributeCreation": t('attribute_detail.run_on_attribute_creation'),
        "runOnAttributeChange": t('attribute_detail.run_on_attribute_change'),
        "template": t('attribute_detail.relation_template'),
        "inherit": t('attribute_detail.inherit'),
        "renderNote": t('attribute_detail.render_note'),
        "widget": t('attribute_detail.widget_relation'),
        "shareCss": t('attribute_detail.share_css'),
        "shareJs": t('attribute_detail.share_js'),
        "shareTemplate": t('attribute_detail.share_template'),
        "shareFavicon": t('attribute_detail.share_favicon')
    }
};
export default class AttributeDetailWidget extends NoteContextAwareWidget {
    async refresh() {
        // switching note/tab should close the widget
        this.hide();
    }
    doRender() {
        this.relatedNotesSpacedUpdate = new SpacedUpdate(async () => this.updateRelatedNotes(), 1000);
        this.$widget = $(TPL);
        shortcutService.bindElShortcut(this.$widget, 'ctrl+return', () => this.saveAndClose());
        shortcutService.bindElShortcut(this.$widget, 'esc', () => this.cancelAndClose());
        this.$title = this.$widget.find('.attr-detail-title');
        this.$inputName = this.$widget.find('.attr-input-name');
        this.$inputName.on('input', ev => {
            if (!ev.originalEvent?.isComposing) { // https://github.com/zadam/trilium/pull/3812
                this.userEditedAttribute();
            }
        });
        this.$inputName.on('change', () => this.userEditedAttribute());
        this.$inputName.on('autocomplete:closed', () => this.userEditedAttribute());
        this.$inputName.on('focus', () => {
            attributeAutocompleteService.initAttributeNameAutocomplete({
                $el: this.$inputName,
                attributeType: () => ['relation', 'relation-definition'].includes(this.attrType) ? 'relation' : 'label',
                open: true
            });
        });
        this.$rowValue = this.$widget.find('.attr-row-value');
        this.$inputValue = this.$widget.find('.attr-input-value');
        this.$inputValue.on('input', ev => {
            if (!ev.originalEvent?.isComposing) { // https://github.com/zadam/trilium/pull/3812
                this.userEditedAttribute();
            }
        });
        this.$inputValue.on('change', () => this.userEditedAttribute());
        this.$inputValue.on('autocomplete:closed', () => this.userEditedAttribute());
        this.$inputValue.on('focus', () => {
            attributeAutocompleteService.initLabelValueAutocomplete({
                $el: this.$inputValue,
                open: true,
                nameCallback: () => this.$inputName.val()
            });
        });
        this.$rowPromoted = this.$widget.find('.attr-row-promoted');
        this.$inputPromoted = this.$widget.find('.attr-input-promoted');
        this.$inputPromoted.on('change', () => this.userEditedAttribute());
        this.$rowPromotedAlias = this.$widget.find('.attr-row-promoted-alias');
        this.$inputPromotedAlias = this.$widget.find('.attr-input-promoted-alias');
        this.$inputPromotedAlias.on('change', () => this.userEditedAttribute());
        this.$rowMultiplicity = this.$widget.find('.attr-row-multiplicity');
        this.$inputMultiplicity = this.$widget.find('.attr-input-multiplicity');
        this.$inputMultiplicity.on('change', () => this.userEditedAttribute());
        this.$rowLabelType = this.$widget.find('.attr-row-label-type');
        this.$inputLabelType = this.$widget.find('.attr-input-label-type');
        this.$inputLabelType.on('change', () => this.userEditedAttribute());
        this.$rowNumberPrecision = this.$widget.find('.attr-row-number-precision');
        this.$inputNumberPrecision = this.$widget.find('.attr-input-number-precision');
        this.$inputNumberPrecision.on('change', () => this.userEditedAttribute());
        this.$rowInverseRelation = this.$widget.find('.attr-row-inverse-relation');
        this.$inputInverseRelation = this.$widget.find('.attr-input-inverse-relation');
        this.$inputInverseRelation.on('input', ev => {
            if (!ev.originalEvent?.isComposing) { // https://github.com/zadam/trilium/pull/3812
                this.userEditedAttribute();
            }
        });
        this.$rowTargetNote = this.$widget.find('.attr-row-target-note');
        this.$inputTargetNote = this.$widget.find('.attr-input-target-note');
        noteAutocompleteService.initNoteAutocomplete(this.$inputTargetNote, {allowCreatingNotes: true})
            .on('autocomplete:noteselected', (event, suggestion, dataset) => {
                if (!suggestion.notePath) {
                    return false;
                }
                const pathChunks = suggestion.notePath.split('/');
                this.attribute.value = pathChunks[pathChunks.length - 1]; // noteId
                this.triggerCommand('updateAttributeList', { attributes: this.allAttributes });
                this.updateRelatedNotes();
            });
        this.$inputInheritable = this.$widget.find('.attr-input-inheritable');
        this.$inputInheritable.on('change', () => this.userEditedAttribute());
        this.$closeAttrDetailButton = this.$widget.find('.close-attr-detail-button');
        this.$closeAttrDetailButton.on('click', () => this.cancelAndClose());
        this.$attrIsOwnedBy = this.$widget.find('.attr-is-owned-by');
        this.$attrSaveDeleteButtonContainer = this.$widget.find('.attr-save-delete-button-container');
        this.$saveAndCloseButton = this.$widget.find('.attr-save-changes-and-close-button');
        this.$saveAndCloseButton.on('click', () => this.saveAndClose());
        this.$deleteButton = this.$widget.find('.attr-delete-button');
        this.$deleteButton.on('click', async () => {
            await this.triggerCommand('updateAttributeList', {
                attributes: this.allAttributes.filter(attr => attr !== this.attribute)
            });
            await this.triggerCommand('saveAttributes');
            this.hide();
        });
        this.$attrHelp = this.$widget.find('.attr-help');
        this.$relatedNotesContainer = this.$widget.find('.related-notes-container');
        this.$relatedNotesTitle = this.$relatedNotesContainer.find('.related-notes-tile');
        this.$relatedNotesList = this.$relatedNotesContainer.find('.related-notes-list');
        this.$relatedNotesMoreNotes = this.$relatedNotesContainer.find('.related-notes-more-notes');
        $(window).on('mousedown', e => {
            if (!$(e.target).closest(this.$widget[0]).length
                && !$(e.target).closest(".algolia-autocomplete").length
                && !$(e.target).closest("#context-menu-container").length) {
                this.hide();
            }
        });
    }
    async showAttributeDetail({allAttributes, attribute, isOwned, x, y, focus}) {
        if (!attribute) {
            this.hide();
            return;
        }
        utils.saveFocusedElement();
        this.attrType = this.getAttrType(attribute);
        const attrName =
            this.attrType === 'label-definition' ? attribute.name.substr(6)
                : (this.attrType === 'relation-definition' ? attribute.name.substr(9) : attribute.name);
        const definition = this.attrType.endsWith('-definition')
            ? promotedAttributeDefinitionParser.parse(attribute.value)
            : {};
        this.$title.text(ATTR_TITLES[this.attrType]);
        this.allAttributes = allAttributes;
        this.attribute = attribute;
        // can be slightly slower so just make it async
        this.updateRelatedNotes();
        this.$attrSaveDeleteButtonContainer.toggle(!!isOwned);
        if (isOwned) {
            this.$attrIsOwnedBy.hide();
        }
        else {
            this.$attrIsOwnedBy
                .show()
                .empty()
                .append(attribute.type === 'label' ? 'Label' : 'Relation')
                .append(` ${t("attribute_detail.is_owned_by_note")} `)
                .append(await linkService.createLink(attribute.noteId))
        }
        this.$inputName
            .val(attrName)
            .attr('readonly', () => !isOwned);
        this.$rowValue.toggle(this.attrType === 'label');
        this.$rowTargetNote.toggle(this.attrType === 'relation');
        this.$rowPromoted.toggle(['label-definition', 'relation-definition'].includes(this.attrType));
        this.$inputPromoted
            .prop("checked", !!definition.isPromoted)
            .attr('disabled', () => !isOwned);
        this.$rowPromotedAlias.toggle(!!definition.isPromoted);
        this.$inputPromotedAlias
            .val(definition.promotedAlias)
            .attr('disabled', () => !isOwned);
        this.$rowMultiplicity.toggle(['label-definition', 'relation-definition'].includes(this.attrType));
        this.$inputMultiplicity
            .val(definition.multiplicity)
            .attr('disabled', () => !isOwned);
        this.$rowLabelType.toggle(this.attrType === 'label-definition');
        this.$inputLabelType
            .val(definition.labelType)
            .attr('disabled', () => !isOwned);
        this.$rowNumberPrecision.toggle(this.attrType === 'label-definition' && definition.labelType === 'number');
        this.$inputNumberPrecision
            .val(definition.numberPrecision)
            .attr('disabled', () => !isOwned);
        this.$rowInverseRelation.toggle(this.attrType === 'relation-definition');
        this.$inputInverseRelation
            .val(definition.inverseRelation)
            .attr('disabled', () => !isOwned);
        if (attribute.type === 'label') {
            this.$inputValue
                .val(attribute.value)
                .attr('readonly', () => !isOwned);
        }
        else if (attribute.type === 'relation') {
            this.$inputTargetNote
                .attr('readonly', () => !isOwned)
                .val("")
                .setSelectedNotePath("");
            if (attribute.value) {
                const targetNote = await froca.getNote(attribute.value);
                if (targetNote) {
                    this.$inputTargetNote
                        .val(targetNote ? targetNote.title : "")
                        .setSelectedNotePath(attribute.value);
                }
            }
        }
        this.$inputInheritable
            .prop("checked", !!attribute.isInheritable)
            .attr('disabled', () => !isOwned);
        this.updateHelp();
        this.toggleInt(true);
        const offset = this.parent.$widget.offset();
        const detPosition = this.getDetailPosition(x, offset);
        this.$widget
            .css("left", detPosition.left)
            .css("right", detPosition.right)
            .css("top", y - offset.top + 70)
            .css("max-height",
                this.$widget.outerHeight() + y > $(window).height() - 50
                    ? $(window).height() - y - 50
                    : 10000);
        if (focus === 'name') {
            this.$inputName
                .trigger('focus')
                .trigger('select');
        }
    }
    getDetailPosition(x, offset) {
        let left = x - offset.left - this.$widget.outerWidth() / 2;
        let right = "";
        if (left < 0) {
            left = 10;
        } else {
            const rightEdge = left + this.$widget.outerWidth();
            if (rightEdge > this.parent.$widget.outerWidth() - 10) {
                left = "";
                right = 10;
            }
        }
        return {left, right};
    }
    async saveAndClose() {
        await this.triggerCommand('saveAttributes');
        this.hide();
        utils.focusSavedElement();
    }
    async cancelAndClose() {
        await this.triggerCommand('reloadAttributes');
        this.hide();
        utils.focusSavedElement();
    }
    userEditedAttribute() {
        this.updateAttributeInEditor();
        this.updateHelp();
        this.relatedNotesSpacedUpdate.scheduleUpdate();
    }
    updateHelp() {
        const attrName = this.$inputName.val();
        if (this.attrType in ATTR_HELP && attrName in ATTR_HELP[this.attrType]) {
            this.$attrHelp
                .empty()
                .append($("")
                    .append($("").text(attrName))
                    .append(" - ")
                    .append(ATTR_HELP[this.attrType][attrName])
                )
                .show();
        }
        else {
            this.$attrHelp.empty().hide();
        }
    }
    async updateRelatedNotes() {
        let {results, count} = await server.post('search-related', this.attribute);
        for (const res of results) {
            res.noteId = res.notePathArray[res.notePathArray.length - 1];
        }
        results = results.filter(({noteId}) => noteId !== this.noteId);
        if (results.length === 0) {
            this.$relatedNotesContainer.hide();
        } else {
            this.$relatedNotesContainer.show();
            this.$relatedNotesTitle.text(t("attribute_detail.other_notes_with_name", {attributeType: this.attribute.type, attributeName: this.attribute.name}));
            this.$relatedNotesList.empty();
            const displayedResults = results.length <= DISPLAYED_NOTES ? results : results.slice(0, DISPLAYED_NOTES);
            const displayedNotes = await froca.getNotes(displayedResults.map(res => res.noteId));
            const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId;
            for (const note of displayedNotes) {
                const notePath = note.getBestNotePathString(hoistedNoteId);
                const $noteLink = await linkService.createLink(notePath, {showNotePath: true});
                this.$relatedNotesList.append(
                    $("").append($noteLink)
                );
            }
            if (results.length > DISPLAYED_NOTES) {
                this.$relatedNotesMoreNotes.show().text(t("attribute_detail.and_more", {count: count - DISPLAYED_NOTES}));
            } else {
                this.$relatedNotesMoreNotes.hide();
            }
        }
    }
    getAttrType(attribute) {
        if (attribute.type === 'label') {
            if (attribute.name.startsWith('label:')) {
                return "label-definition";
            } else if (attribute.name.startsWith('relation:')) {
                return "relation-definition";
            } else {
                return "label";
            }
        }
        else if (attribute.type === 'relation') {
            return "relation";
        }
        else {
            this.$title.text('');
        }
    }
    updateAttributeInEditor() {
        let attrName = this.$inputName.val();
        if (!utils.isValidAttributeName(attrName)) {
            // invalid characters are simply ignored (from user perspective they are not even entered)
            attrName = utils.filterAttributeName(attrName);
            this.$inputName.val(attrName);
        }
        if (this.attrType === 'label-definition') {
            attrName = `label:${attrName}`;
        } else if (this.attrType === 'relation-definition') {
            attrName = `relation:${attrName}`;
        }
        this.attribute.name = attrName;
        this.attribute.isInheritable = this.$inputInheritable.is(":checked");
        if (this.attrType.endsWith('-definition')) {
            this.attribute.value = this.buildDefinitionValue();
        }
        else if (this.attrType === 'relation') {
            this.attribute.value = this.$inputTargetNote.getSelectedNoteId();
        }
        else {
            this.attribute.value = this.$inputValue.val();
        }
        this.triggerCommand('updateAttributeList', { attributes: this.allAttributes });
    }
    buildDefinitionValue() {
        const props = [];
        if (this.$inputPromoted.is(":checked")) {
            props.push("promoted");
            if (this.$inputPromotedAlias.val() !== '') {
                props.push(`alias=${this.$inputPromotedAlias.val()}`);
            }
        }
        props.push(this.$inputMultiplicity.val());
        if (this.attrType === 'label-definition') {
            props.push(this.$inputLabelType.val());
            if (this.$inputLabelType.val() === 'number' && this.$inputNumberPrecision.val() !== '') {
                props.push(`precision=${this.$inputNumberPrecision.val()}`);
            }
        } else if (this.attrType === 'relation-definition' && this.$inputInverseRelation.val().trim().length > 0) {
            const inverseRelationName = this.$inputInverseRelation.val();
            props.push(`inverse=${utils.filterAttributeName(inverseRelationName)}`);
        }
        this.$rowNumberPrecision.toggle(
            this.attrType === 'label-definition'
            && this.$inputLabelType.val() === 'number');
        this.$rowPromotedAlias.toggle(this.$inputPromoted.is(":checked"));
        return props.join(",");
    }
    hide() {
        this.toggleInt(false);
    }
    createLink(noteId) {
        return $("", {
            href: `#root/${noteId}`,
            class: 'reference-link'
        });
    }
    async noteSwitched() {
        this.hide();
    }
}