import noteDetailService from '../services/note_detail.js';
import server from '../services/server.js';
import infoService from "../services/info.js";
import treeUtils from "../services/tree_utils.js";
import linkService from "../services/link.js";

const $dialog = $("#attributes-dialog");
const $saveAttributesButton = $("#save-attributes-button");
const $ownedAttributesBody = $('#owned-attributes-table tbody');

const attributesModel = new AttributesModel();

function AttributesModel() {
    const self = this;

    this.ownedAttributes = ko.observableArray();
    this.inheritedAttributes = ko.observableArray();

    this.availableTypes = [
        { text: "Label", value: "label" },
        { text: "Label definition", value: "label-definition" },
        { text: "Relation", value: "relation" },
        { text: "Relation definition", value: "relation-definition" }
    ];

    this.availableLabelTypes = [
        { text: "Text", value: "text" },
        { text: "Number", value: "number" },
        { text: "Boolean", value: "boolean" },
        { text: "Date", value: "date" }
    ];

    this.multiplicityTypes = [
        { text: "Single value", value: "singlevalue" },
        { text: "Multi value", value: "multivalue" }
    ];

    this.typeChanged = function(data, event) {
        self.getTargetAttribute(event.target).valueHasMutated();
    };

    this.labelTypeChanged = function(data, event) {
        self.getTargetAttribute(event.target).valueHasMutated();
    };

    this.updateAttributePositions = function() {
        let position = 0;

        // we need to update positions by searching in the DOM, because order of the
        // attributes in the viewmodel (self.ownedAttributes()) stays the same
        $ownedAttributesBody.find('input[name="position"]').each(function() {
            const attribute = self.getTargetAttribute(this);

            attribute().position = position++;
        });
    };

    async function showAttributes(attributes) {
        const ownedAttributes = attributes.filter(attr => attr.isOwned);

        for (const attr of ownedAttributes) {
            attr.labelValue = attr.type === 'label' ? attr.value : '';
            attr.relationValue = attr.type === 'relation' ? (await treeUtils.getNoteTitle(attr.value) + " (" + attr.value + ")") : '';
            attr.labelDefinition = (attr.type === 'label-definition' && attr.value) ? attr.value : {
                labelType: "text",
                multiplicityType: "singlevalue",
                isPromoted: true
            };
            attr.relationDefinition = attr.type === ('relation-definition' && attr.value) ? attr.value : {
                multiplicityType: "singlevalue",
                isPromoted: true
            };

            delete attr.value;
        }

        self.ownedAttributes(ownedAttributes.map(ko.observable));

        addLastEmptyRow();

        const inheritedAttributes = attributes.filter(attr => !attr.isOwned);

        self.inheritedAttributes(inheritedAttributes);
    }

    this.loadAttributes = async function() {
        const noteId = noteDetailService.getCurrentNoteId();

        const attributes = await server.get('notes/' + noteId + '/attributes');

        await showAttributes(attributes);

        // attribute might not be rendered immediatelly so could not focus
        setTimeout(() => $(".attribute-name:last").focus(), 100);

        $ownedAttributesBody.sortable({
            handle: '.handle',
            containment: $ownedAttributesBody,
            update: this.updateAttributePositions
        });
    };

    this.deleteAttribute = function(data, event) {
        const attribute = self.getTargetAttribute(event.target);
        const attributeData = attribute();

        if (attributeData) {
            attributeData.isDeleted = true;

            attribute(attributeData);

            addLastEmptyRow();
        }
    };

    function isValid() {
        for (let attributes = self.ownedAttributes(), i = 0; i < attributes.length; i++) {
            if (self.isEmptyName(i)) {
                return false;
            }
        }

        return true;
    }

    this.save = async function() {
        // we need to defocus from input (in case of enter-triggered save) because value is updated
        // on blur event (because of conflict with jQuery UI Autocomplete). Without this, input would
        // stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel.
        $saveAttributesButton.focus();

        if (!isValid()) {
            alert("Please fix all validation errors and try saving again.");
            return;
        }

        self.updateAttributePositions();

        const noteId = noteDetailService.getCurrentNoteId();

        const attributesToSave = self.ownedAttributes()
            .map(attribute => attribute())
            .filter(attribute => attribute.attributeId !== "" || attribute.name !== "");

        for (const attr of attributesToSave) {
            if (attr.type === 'label') {
                attr.value = attr.labelValue;
            }
            else if (attr.type === 'relation') {
                attr.value = treeUtils.getNoteIdFromNotePath(linkService.getNotePathFromLabel(attr.relationValue));
            }
            else if (attr.type === 'label-definition') {
                attr.value = attr.labelDefinition;
            }
            else if (attr.type === 'relation-definition') {
                attr.value = attr.relationDefinition;
            }

            delete attr.labelValue;
            delete attr.relationValue;
            delete attr.labelDefinition;
            delete attr.relationDefinition;
        }

        const attributes = await server.put('notes/' + noteId + '/attributes', attributesToSave);

        await showAttributes(attributes);

        infoService.showMessage("Attributes have been saved.");

        noteDetailService.loadAttributes();
    };

    function addLastEmptyRow() {
        const attributes = self.ownedAttributes().filter(attr => !attr().isDeleted);
        const last = attributes.length === 0 ? null : attributes[attributes.length - 1]();

        if (!last || last.name.trim() !== "") {
            self.ownedAttributes.push(ko.observable({
                attributeId: '',
                type: 'label',
                name: '',
                labelValue: '',
                relationValue: '',
                isInheritable: false,
                isDeleted: false,
                position: 0,
                labelDefinition: {
                    labelType: "text",
                    multiplicityType: "singlevalue",
                    isPromoted: true
                },
                relationDefinition: {
                    multiplicityType: "singlevalue",
                    isPromoted: true
                }
            }));
        }
    }

    this.attributeChanged = function (data, event) {
        addLastEmptyRow();

        const attribute = self.getTargetAttribute(event.target);

        attribute.valueHasMutated();
    };

    this.isNotUnique = function(index) {
        const cur = self.ownedAttributes()[index]();

        if (cur.name.trim() === "") {
            return false;
        }

        for (let attributes = self.ownedAttributes(), i = 0; i < attributes.length; i++) {
            const attribute = attributes[i]();

            if (index !== i && cur.name === attribute.name && cur.type === attribute.type) {
                return true;
            }
        }

        return false;
    };

    this.isEmptyName = function(index) {
        const cur = self.ownedAttributes()[index]();

        return cur.name.trim() === "" && !cur.isDeleted && (cur.attributeId !== "" || cur.labelValue !== "" || cur.relationValue);
    };

    this.getTargetAttribute = function(target) {
        const context = ko.contextFor(target);
        const index = context.$index();

        return self.ownedAttributes()[index];
    }
}

async function showDialog() {
    glob.activeDialog = $dialog;

    await attributesModel.loadAttributes();

    $dialog.dialog({
        modal: true,
        width: 950,
        height: 500
    });
}

ko.applyBindings(attributesModel, $dialog[0]);

$dialog.on('focus', '.attribute-name', function (e) {
    if (!$(this).hasClass("ui-autocomplete-input")) {
        $(this).autocomplete({
            source: async (request, response) => {
                const attribute = attributesModel.getTargetAttribute(this);
                const type = (attribute().type === 'relation' || attribute().type === 'relation-definition') ? 'relation' : 'label';
                const names = await server.get('attributes/names/?type=' + type + '&query=' + encodeURIComponent(request.term));
                const result = names.map(name => {
                    return {
                        label: name,
                        value: name
                    }
                });

                if (result.length > 0) {
                    response(result);
                }
                else {
                    response([{
                        label: "No results",
                        value: "No results"
                    }]);
                }
            },
            minLength: 0
        });
    }

    $(this).autocomplete("search", $(this).val());
});

$dialog.on('focus', '.label-value', async function (e) {
    if (!$(this).hasClass("ui-autocomplete-input")) {
        const attributeName = $(this).parent().parent().find('.attribute-name').val();

        if (attributeName.trim() === "") {
            return;
        }

        const attributeValues = await server.get('attributes/values/' + encodeURIComponent(attributeName));

        if (attributeValues.length === 0) {
            return;
        }

        $(this).autocomplete({
            // shouldn't be required and autocomplete should just accept array of strings, but that fails
            // because we have overriden filter() function in autocomplete.js
            source: attributeValues.map(attribute => {
                return {
                    attribute: attribute,
                    value: attribute
                }
            }),
            minLength: 0
        });
    }

    $(this).autocomplete("search", $(this).val());
});

export default {
    showDialog
};