/**
 * Copyright 2025 Adobe
 * All Rights Reserved.
 */
define([
    'jquery',
    'underscore',
    'Magento_Ui/js/modal/prompt',
    'Magento_Ui/js/modal/alert',
    'jquery/jstree/jquery.jstree'
], function ($, _, prompt, alert) {
    'use strict';

    /**
     * Create node object
     *
     * @param {Object} data
     * @param {Object} [instance=null]
     * @return {Object}
     * @private
     */
    function createNode(data, instance) {
        const node = instance || {},
            isFalsy = (value) => ['0', 0, false].indexOf(value) !== -1;

        Object.assign(
            node,
            {
                text: null,
                entity_id: null,
                cls: 'leaf',
                allowDrag: false,
                allowDrop: false,
                is_user_defined: false,
                is_unassignable: false
            },
            data,
            {
                allowDrag: !isFalsy(data.allowDrag),
                allowDrop: !isFalsy(data.allowDrop),
                is_user_defined: !isFalsy(data.is_user_defined),
                is_unassignable: !isFalsy(data.is_unassignable)
            }
        );
        node._id = data._id || data.id;
        node.li_attr = {class: node.cls};
        node.state = {opened: true, ...node.state || {}};
        if (Array.isArray(node.children)) {
            node.children = node.children.map((child) => createNode(child));
        }
        // A unique ID will be generated by jstree
        delete node.id;
        if (node._id === true) {
            node._id = null;
        }
        return node;
    }

    /**
     * @param {Tree} tree
     * @param {Object} config
     * @return {Object}
     * @private
     */
    function _Tree(tree, config) {
        const settings = {core: {}, ...config.settings || {}},
            readOnly = config.readOnly || false,
            events = config.events || {trigger: () => !readOnly},
            selector = config.selector,
            validations = config.validations || {},
            initialData = (config.data || []).map((node) => createNode(node)),
            placeholder = config.placeholder ? createNode({
                id: 'empty',
                text: 'Empty',
                cls: 'folder',
                allowDrop: false,
                allowDrag: false,
                ..._.isObject(config.placeholder) ? config.placeholder : {},
                ...initialData.length === 1 && initialData[0]._id === 'empty' ? initialData[0] : {}
            }) : null;

        /**
         * Validate value against rule
         *
         * @param {String} rule
         * @param {String} value
         * @param {Function} [callback=null]
         * @return {Boolean}
         */
        function validate(rule, value, callback) {
            const validators = {
                isNotEmpty: () => !!value,
                isUnique: () => {
                    const rootNode = tree.root();
                    let result = true;

                    for (let i = 0; i < rootNode.children.length; i++) {
                        let node = tree.get(rootNode.children[i]);

                        if (node.text.toLowerCase() === value.toLowerCase() && (!callback || callback(node))) {
                            result = false;
                            break;
                        }
                    }
                    return result;
                }
            };

            return validators[rule]();
        }

        /**
         * Validate node property
         *
         * @param {String} property
         * @param {String} value
         * @param {Function} [callback=null]
         * @return {Boolean}
         */
        function isValid(property, value, callback) {
            if (!validations[property] || validations[property].length < 1) {
                return true;
            }

            value = value.trim();

            const result = {property, value, code: null, isValid: true};

            for (let i = 0; i < validations[property].length; i++) {
                if (!validate(validations[property][i], value, callback)) {
                    result.code = validations[property][i];
                    result.isValid = false;
                    break;
                }
            }
            return (events.trigger('afterValidate', {tree, result}) || result).isValid;
        }

        /**
         * Check if node is draggable
         *
         * @param {Object[]} nodes
         * @return {Boolean}
         */
        function isDraggable(nodes) {
            for (let i = 0; i < nodes.length; i++) {
                if (!events.trigger('beforeDrag', {tree, node: nodes[i]})) {
                    return false;
                }
            }
            return true;
        }

        /**
         * Check if operation is allowed
         *
         * @param {String} operation
         * @param {Object} node
         * @param {Object} parent
         * @param {Number|String} position
         * @param {Object} more
         * @return {Boolean}
         */
        function check(operation, node, parent, position, more) {
            let result;

            switch (operation) {
            case 'move_node':
                result = events.trigger('beforeMove', {tree, node, parent, position, more});
                break;
            case 'copy_node':
                result = events.trigger('beforeCopy', {tree, node, parent, position, more});
                break;
            case 'create_node':
                createNode({...node}, node);
                result = placeholder && placeholder._id === node._id && (node.id = node._id)
                    || events.trigger('beforeCreate', {tree, node, parent, position, more});
                if (result) {
                    result = isValid('text', node.text);
                }
                break;
            case 'delete_node':
                result = events.trigger('beforeDelete', {tree, node, parent, position, more});
                break;
            case 'edit':
                position = node.text;
                // eslint-disable-next-line no-fallthrough
            case 'rename_node':
                result = events.trigger('beforeRename', {tree, node, parent, name: position, more});
                if (result) {
                    result = isValid('text', position, (item) => item.id !== node.id);
                }
                break;
            default:
                result = true;
            }

            return result;
        }

        /**
         * Register listeners
         */
        function registerListeners() {
            const jElem = $(selector);

            jElem.on('copy_node.jstree', function (e, data) {
                data.node.original = data.original.original;
                if (placeholder && tree.root().children.length > 0) {
                    let node = tree.get(placeholder._id);

                    if (node) {
                        tree.delete(node);
                    }
                }
                events.trigger('afterCopy', {tree, ...data});
            });

            jElem.on('delete_node.jstree', function (e, data) {
                if (placeholder
                    && tree.root().children.length === 0
                    && !tree.get(placeholder._id)
                ) {
                    tree.append({...placeholder, id: placeholder._id});
                }
                events.trigger('afterDelete', {tree, ...data});
            });

            jElem.on('dblclick.jstree', function () {
                const selectedNode = tree.getSelected();

                if (selectedNode && tree.isGroup(selectedNode)) {
                    tree.edit(selectedNode);
                }
            });

            jElem.on('create_node.jstree', function (e, data) {
                events.trigger('afterCreate', {tree, ...data});
            });

            jElem.on('move_node.jstree', function (e, data) {
                events.trigger('afterMove', {tree, ...data});
            });

            jElem.on('rename_node.jstree', function (e, data) {
                events.trigger('afterRename', {tree, ...data});
            });
        }

        if (placeholder
            && (initialData.length === 0 || initialData.length === 1 && initialData[0]._id === placeholder._id)
        ) {
            initialData[0] = {...placeholder, id: placeholder._id};
        }

        registerListeners();

        settings.core = {
            ...settings.core || {},
            data: initialData,
            dblclick_toggle: false,
            check_callback: readOnly ? false : check
        };
        settings.dnd = {
            inside_pos: 'last',
            ...settings.dnd || {},
            is_draggable: readOnly ? false : isDraggable
        };
        settings.plugins = [
            ...settings.plugins || [],
            'dnd'
        ];

        return $(selector).jstree(settings).jstree(true);
    }

    /**
     * Tree class
     *
     * @param {Object} config
     * @constructor
     */
    function Tree(config) {
        this.instance = _Tree(this, config);
    }

    /**
     * Get root node
     *
     * @return {Object}
     */
    Tree.prototype.root = function () {
        return this.get($.jstree.root);
    };

    /**
     * Get node
     *
     * @param {Object|String} node
     * @return {Object}
     */
    Tree.prototype.get = function (node) {
        return this.instance.get_node(node);
    };

    /**
     * Get node parent
     *
     * @param {Object|String} node
     * @return {Object|null}
     */
    Tree.prototype.parent = function (node) {
        const parent = this.instance.get_parent(node);

        return parent ? this.get(parent) : null;
    };

    /**
     * Append node to the tree
     *
     * @param {Object} data
     * @param {Object|String|null} [parent=null]
     * @return {String|Boolean} - the ID of the newly create node
     */
    Tree.prototype.append = function (data, parent) {
        return this.create(data, parent, 'last');
    };

    /**
     * Prepend node to the tree
     *
     * @param {Object} data
     * @param {Object|String|null} [parent=null]
     * @return {String|Boolean} - the ID of the newly create node
     */
    Tree.prototype.prepend = function (data, parent) {
        return this.create(data, parent, 'first');
    };

    /**
     * Prepend node to the tree
     *
     * @param {Object} data
     * @param {Object|String|null} [parent=null]
     * @param {String|Number|null} [position=null] - 'first', 'last', 'before', 'after', 'inside', 0, 1, 2, ...
     * @return {String|Boolean} - the ID of the newly create node
     */
    Tree.prototype.create = function (data, parent, position) {
        position = !_.isUndefined(position) ? position : 'last';
        return this.instance.create_node(parent ? this.get(parent) : null, data, position);
    };

    /**
     * Copy nodes into the tree in specified position
     *
     * @param {Object[]} nodes
     * @param {Object|String|null} [parent=null]
     * @param {String|Number|null} [position=null] - 'first', 'last', 'before', 'after', 'inside', 0, 1, 2, ...
     * @return {String|Boolean}
     */
    Tree.prototype.copy = function (nodes, parent, position) {
        position = !_.isUndefined(position) ? position : 'last';
        return this.instance.copy_node(nodes, parent ? parent : this.root(), position);
    };

    /**
     * Move nodes in specified position
     *
     * @param {Object[]} nodes
     * @param {Object|String|null} [parent=null]
     * @param {String|Number|null} [position=null] - 'first', 'last', 'before', 'after', 'inside', 0, 1, 2, ...
     * @return {String|Boolean}
     */
    Tree.prototype.move = function (nodes, parent, position) {
        position = !_.isUndefined(position) ? position : 'last';
        return this.instance.move_node(nodes, parent ? parent : this.root(), position);
    };

    /**
     * Delete node from the tree
     *
     * @param {Object|String} node
     * @return {Boolean}
     */
    Tree.prototype.delete = function (node) {
        return this.instance.delete_node(node);
    };

    /**
     * Rename node
     *
     * @param {Object|String} node
     * @param {String|null} [text=null]
     * @return {Boolean}
     */
    Tree.prototype.rename = function (node, text) {
        return this.instance.rename_node(node, text);
    };

    /**
     * Edit node
     *
     * @param {Object|String} node
     * @param {String|null} [text=null]
     */
    Tree.prototype.edit = function (node, text) {
        this.instance.edit(node, text);
    };

    /**
     * Expand node
     *
     * @param {Object|String} node
     */
    Tree.prototype.open = function (node) {
        this.instance.open_node(node);
    };

    /**
     * Get selected node
     *
     * @return {Object}
     */
    Tree.prototype.getSelected = function () {
        return this.instance.get_selected(true)[0];
    };

    /**
     * Get original node object
     *
     * @param {Object|String} node
     * @return {Object}
     */
    Tree.prototype.getOriginalNode = function (node) {
        let nodeObj = _.isObject(node) ? node : this.get(node);

        return this.isRoot(node)
            ? null
            : nodeObj.original || nodeObj;
    };

    /**
     * Check if node is root
     *
     * @param {Object|String} node
     * @return {Boolean}
     */
    Tree.prototype.isRoot = function (node) {
        let nodeObj = _.isObject(node) ? node : this.get(node);

        return nodeObj.id === $.jstree.root;
    };

    /**
     * Check if node is a group
     *
     * @param {Object|String} node
     * @return {Boolean}
     */
    Tree.prototype.isGroup = function (node) {
        let nodeObj = _.isObject(node) ? node : this.get(node);

        return !this.isRoot(nodeObj) && this.getOriginalNode(nodeObj).cls === 'folder';
    };

    /**
     * Find the first child with given parent node and matching a set of property and value pairs
     *
     * @param {Object|String|null} [parent=null]
     * @param {Object[]|null} [conditions=null]
     * @param {Boolean|null} [recursive=null]
     * @return {Object}
     */
    Tree.prototype.find = function (parent, conditions, recursive) {
        const _self = this,
            parentNode = parent ? _self.get(parent) : _self.root();

        recursive = recursive || false;
        conditions = conditions || [];
        conditions = _.isArray(conditions) ? conditions : [conditions];
        for (let i = 0; i < parentNode.children.length; i++) {
            let childNode = _self.get(parentNode.children[i]);

            if (!conditions.length
                || conditions.some((condition) => _.isMatch(this.getOriginalNode(childNode), condition))
                || recursive && (childNode = _self.find(childNode, conditions, recursive))
            ) {
                return childNode;
            }
        }
        return null;
    };

    /**
     * Find all children with given parent node and matching a set of property and value pairs
     *
     * @param {Object|String|null} [parent=null]
     * @param {Object[]|null} [conditions=null] - array of conditions to match. OR logic is applied to conditions
     * @param {Boolean|null} [recursive=null]
     * @return {Object[]}
     */
    Tree.prototype.findAll = function (parent, conditions, recursive) {
        const _self = this,
            parentNode = parent ? _self.get(parent) : _self.root(),
            children = [];

        recursive = recursive || false;
        conditions = conditions || [];
        conditions = _.isArray(conditions) ? conditions : [conditions];
        for (let i = 0; i < parentNode.children.length; i++) {
            let childNode = _self.get(parentNode.children[i]);

            if (!conditions.length
                || conditions.some((condition) => _.isMatch(this.getOriginalNode(childNode), condition))
            ) {
                children.push(_self.get(childNode));
            }

            if (recursive) {
                children.push(..._self.findAll(childNode, conditions, recursive));
            }
        }

        return children;
    };

    /**
     * Editor class
     *
     * @param {Object} [config=null]
     * @return {Editor}
     * @constructor
     */
    function Editor(config) {
        if (!(this instanceof Editor)) {
            return new Editor(config);
        }

        config = config || {
            readOnly: false,
            tree: {
                assigned: {},
                unassigned: {}
            }
        };

        const events = {
            trigger: config.readOnly ? () => false : (name, data) => this[name](data.tree, data)
        };

        this.saveUrl = config.saveUrl;

        this.errors = {
            save: {
                message: 'An error occurred while saving changes.'
            },
            unassign: {
                message: 'This attribute cannot be unassigned.'
            },
            delete_group: {
                message: 'This group cannot be deleted.'
            },
            group_not_selected: {
                message: 'Please select a group.'
            },
            validation: {
                isNotEmpty: {
                    message: ':text cannot be empty.',
                    bindings: {
                        ':text': 'property'
                    }
                },
                isUnique: {
                    message: ':value is already in use.',
                    bindings: {
                        ':value': 'value'
                    }
                }
            },
            ...config.errors || {}
        };

        this.newGroupModal = {
            title: '',
            content: '',
            ...config.newGroupModal || {}
        };

        this.assigned = new Tree({
            events,
            readOnly: config.readOnly,
            settings: {
                core: {
                    animation: false
                }
            },
            validations: {
                text: ['isNotEmpty', 'isUnique']
            },
            ...config.tree.assigned
        });

        this.unassigned = new Tree({
            events,
            readOnly: config.readOnly,
            settings: {
                core: {
                    themes: {
                        dots: false
                    }
                }
            },
            ...config.tree.unassigned
        });

        this.req = {
            attributes: [],
            groups: [],
            not_attributes: [],
            removeGroups: []
        };

        if (config.readOnly) {
            this.submit = function () {
                this.error(Editor.ERRORS.READ_ONLY);
            };
            this.save = function () {
                this.error(Editor.ERRORS.READ_ONLY);
            };
            this.addGroup = function () {
                this.error(Editor.ERRORS.READ_ONLY);
            };
        }

        return this;
    }

    /**
     * Tree constructor
     *
     * @type {Function}
     * @static
     */
    Editor.Tree = Tree;

    /**
     * @type {Object}
     * @static
     */
    Editor.ERRORS = {
        READ_ONLY: 'read_only',
        SAVE: 'save',
        VALIDATION: 'validation',
        DELETE_GROUP: 'delete_group',
        UNASSIGN: 'unassign',
        GROUP_NOT_SELECTED: 'group_not_selected'
    };

    /**
     * Callback that is called to check if to node can be dragged
     *
     * @param {Tree} tree
     * @param {Object} data
     * @return {Boolean}
     */
    Editor.prototype.beforeDrag = function (tree, data) {
        return tree.getOriginalNode(data.node).allowDrag;
    };

    /**
     * Callback that is called to check if node can be moved
     *
     * @param {Tree} tree
     * @param {Object} data
     * @return {Boolean}
     */
    Editor.prototype.beforeMove = function (tree, data) {
        if (tree === this.unassigned) {
            return tree.isRoot(data.parent) && !tree.isGroup(data.node);
        }

        return tree === this.assigned
            && (
                tree.isGroup(data.node)
                && tree.isRoot(data.parent)
                || !tree.isGroup(data.node)
                && tree.isGroup(data.parent)
                && tree.getOriginalNode(data.parent).allowDrop
            );
    };

    /**
     * Callback that is called to check if node can be moved to another tree
     *
     * @param {Tree} tree
     * @param {Object} data
     * @return {Boolean}
     */
    Editor.prototype.beforeCopy = function (tree, data) {
        if (tree === this.unassigned) {
            return this.beforeUnassign(tree, data);
        }
        return this.beforeAssign(tree, data);
    };

    /**
     * Callback that is called to check if node can be moved to "assigned" tree
     *
     * @param {Tree} tree destination tree
     * @param {Object} data
     * @return {Boolean}
     */
    Editor.prototype.beforeAssign = function (tree, data) {
        return tree.isGroup(data.node)
            && tree.isRoot(data.parent)
            || !tree.isGroup(data.node)
            && tree.isGroup(data.parent)
            && tree.getOriginalNode(data.parent).allowDrop;
    };

    /**
     * Callback that is called to check if node can be moved to "unassigned" tree
     *
     * @param {Tree} tree destination tree
     * @param {Object} data
     * @return {Boolean}
     */
    Editor.prototype.beforeUnassign = function (tree, data) {
        let result;

        if (tree.isGroup(data.node)) {
            result = false;
        } else if (tree.getOriginalNode(data.node).is_unassignable === false) {
            this.error(Editor.ERRORS.UNASSIGN, {tree: this.assigned, node: data.node});
            result = false;
        } else {
            result = tree.isRoot(data.parent);
        }
        return result;
    };

    /**
     * Callback that is called to check if node can be renamed
     *
     * @param {Tree} tree
     * @param {Object} data
     * @return {Boolean}
     */
    Editor.prototype.beforeRename = function (tree, data) {
        return tree === this.assigned && tree.isGroup(data.node);
    };

    /**
     * Callback that is called to check if node can be deleted
     *
     * @param {Tree} tree
     * @param {Object} data
     * @return {Boolean}
     */
    Editor.prototype.beforeDelete = function (tree, data) {
        let result;

        if (tree === this.assigned) {
            if (tree.isGroup(data.node) && this.assigned.find(data.node, [{is_unassignable: false}])) {
                this.error(Editor.ERRORS.DELETE_GROUP, {node: data.node, tree});
                result = false;
            } else {
                result = tree.isGroup(data.node) || tree.getOriginalNode(data.node).is_unassignable !== false;
            }
        } else {
            result = true;
        }
        return result;
    };

    /**
     * Callback that is called to check if node can be created
     *
     * @param {Tree} tree
     * @param {Object} data
     * @return {Boolean}
     */
    Editor.prototype.beforeCreate = function (tree, data) {
        return tree === this.assigned
            && tree.isRoot(data.parent)
            && tree.isGroup(data.node);
    };

    /**
     * Callback that is called after node is moved to another tree
     *
     * @param {Tree} tree
     * @param {Object} data
     */
    Editor.prototype.afterCopy = function (tree, data) {
        if (tree.isGroup(data.parent)) {
            tree.open(data.parent);
        }
    };

    /**
     * Callback that is called after node is deleted
     *
     * @param {Tree} tree
     * @param {Object} data
     */
    Editor.prototype.afterDelete = function (tree, data) {
        if (tree !== this.assigned || !tree.isGroup(data.node)) {
            return;
        }

        const originalNode = tree.getOriginalNode(data.node);

        if (originalNode._id) {
            this.req.removeGroups.push(originalNode._id);
        }

        this.unassigned.copy(tree.findAll(data.node, [{is_user_defined: true}]));
    };

    /**
     * Callback that is called after node name is validated
     *
     * @param {Tree} tree
     * @param {Object} data
     */
    Editor.prototype.afterValidate = function (tree, data) {
        if (!data.result.isValid) {
            this.error(Editor.ERRORS.VALIDATION, {tree, result: data.result});
        }
        return data.result;
    };

    /**
     * Callback that is called after node is created
     *
     * @param {Tree} tree
     * @param {Object} data
     */
    // eslint-disable-next-line no-unused-vars
    Editor.prototype.afterCreate = function (tree, data) {};

    /**
     * Callback that is called after node is renamed
     *
     * @param {Tree} tree
     * @param {Object} data
     */
    // eslint-disable-next-line no-unused-vars
    Editor.prototype.afterRename = function (tree, data) {};

    /**
     * Callback that is called after node is moved
     *
     * @param {Tree} tree
     * @param {Object} data
     */
    // eslint-disable-next-line no-unused-vars
    Editor.prototype.afterMove = function (tree, data) {};

    /**
     * Callback that is called when an error occurs
     *
     * @param {string} code
     * @param {Object} data
     */
    // eslint-disable-next-line no-unused-vars
    Editor.prototype.error = function (code, data) {
        data = data || {};
        switch (code) {
        case Editor.ERRORS.SAVE:
            alert({content: data.message || this.errors.save.message});
            break;
        case Editor.ERRORS.VALIDATION:
            let message = data.result.message;

            if (!message && this.errors.validation[data.result.code]) {
                message = this.errors.validation[data.result.code].message;

                _.each(this.errors.validation[data.result.code].bindings || {}, function (value, key) {
                    message = message.replace(key, data.result[value]);
                });
            }
            message && alert({content: message});
            break;
        case Editor.ERRORS.DELETE_GROUP:
            alert({content: data.message || this.errors.delete_group.message});
            break;
        case Editor.ERRORS.UNASSIGN:
            alert({content: data.message || this.errors.unassign.message});
            break;
        case Editor.ERRORS.GROUP_NOT_SELECTED:
            alert({content: data.message || this.errors.group_not_selected.message});
            break;
        }
    };

    /**
     * Callback that is called after save fails
     */
    Editor.prototype.failure = function () {
        this.error(Editor.ERRORS.SAVE);
    };

    /**
     * Delete selected node
     */
    Editor.prototype.submit = function () {
        const node = this.assigned.getSelected();

        if (!node) {
            this.error(Editor.ERRORS.GROUP_NOT_SELECTED);
            return;
        }

        if (!this.assigned.isGroup(node)) {
            return;
        }

        this.assigned.delete(node);
    };

    /**
     * Callback that is called before saving changes
     */
    Editor.prototype.beforeSave = function () {};

    /**
     * Callback that is called after save
     *
     * @param {Object} response
     */
    Editor.prototype.afterSave = function (response) {
        if (response.error || response.message) {
            this.error(Editor.ERRORS.SAVE, {message: response.message});
        } else if (response.ajaxExpired && response.ajaxRedirect) {
            window.setLocation(response.ajaxRedirect);
        } else if (response.url) {
            window.setLocation(response.url);
        }
    };

    /**
     * Save changes
     */
    Editor.prototype.save = function () {
        const _self = this,
            isAttributeAssigned = {},
            assignedAttributes = [],
            unassignedAttributes = [],
            groups = [];

        _self.assigned.findAll().forEach(function (groupNode, gIndex) {
            let groupId = _self.assigned.getOriginalNode(groupNode)._id || groupNode.id;

            groups.push([groupId, groupNode.text.trim(), gIndex + 1]);

            _self.assigned.findAll(groupNode).forEach(function (attributeNode, aIndex) {
                let originalNode = _self.assigned.getOriginalNode(attributeNode),
                    attributeId = originalNode._id;

                if (attributeId > 0 && isAttributeAssigned[attributeId] === undefined) {
                    isAttributeAssigned[attributeId] = true;
                    assignedAttributes.push([attributeId, groupId, aIndex + 1, originalNode.entity_id]);
                }
            });
        });

        _self.unassigned.findAll().forEach(function (attributeNode) {
            let originalNode = _self.assigned.getOriginalNode(attributeNode),
                attributeId = originalNode._id;

            if (attributeId > 0 && originalNode.entity_id > 0) {
                unassignedAttributes.push(originalNode.entity_id);
            }
        });

        _self.req.groups = groups;
        _self.req.attributes = assignedAttributes;
        _self.req.not_attributes = unassignedAttributes;
        _self.req.removeGroups = _.uniq(_self.req.removeGroups);

        this.beforeSave();

        $.ajax({
            url: _self.saveUrl,
            type: 'POST',
            data: {data: JSON.stringify(_self.req)},
            success: _self.afterSave.bind(_self),
            error: _self.failure.bind(_self)
        });
    };

    /**
     * Add new group to the assigned tree
     *
     * @param {String} [name=null]
     * @return {String}
     */
    Editor.prototype.addGroup = function (name) {
        const _self = this,
            add = (text) => _self.assigned.append({
                text: text.trim(),
                icon: 'jstree-folder',
                cls: 'folder',
                allowDrop: true,
                allowDrag: true
            });

        if (name) {
            return add(name);
        }

        prompt({
            title: this.newGroupModal.title,
            content: this.newGroupModal.content,
            value: '',
            validation: true,
            validationRules: ['required-entry'],
            attributesForm: {
                novalidate: 'novalidate',
                action: ''
            },
            attributesField: {
                name: 'name',
                'data-validate': '{required:true}',
                maxlength: '255'
            },
            actions: {
                confirm: function (group_name) {
                    add(group_name);
                }
            }
        });
    };

    return Editor;
});
