/**
 * The default implementation of the class used for `{@link Ext.list.Tree}`.
 *
 * This class can only be used in conjunction with {@link Ext.list.Tree}.
 * @since 6.0.0
 */
Ext.define('Ext.list.TreeItem', {
    extend: 'Ext.list.AbstractTreeItem',
    xtype: 'treelistitem',
 
    collapsedCls: Ext.baseCSSPrefix + 'treelist-item-collapsed',
    expandedCls: Ext.baseCSSPrefix + 'treelist-item-expanded',
    floatedToolCls: Ext.baseCSSPrefix + 'treelist-item-tool-floated',
    leafCls: Ext.baseCSSPrefix + 'treelist-item-leaf',
    expandableCls: Ext.baseCSSPrefix + 'treelist-item-expandable',
    hideIconCls: Ext.baseCSSPrefix + 'treelist-item-hide-icon',
    loadingCls: Ext.baseCSSPrefix + 'treelist-item-loading',
    selectedCls: Ext.baseCSSPrefix + 'treelist-item-selected',
    selectedParentCls: Ext.baseCSSPrefix + 'treelist-item-selected-parent',
    withIconCls: Ext.baseCSSPrefix + 'treelist-item-with-icon',
    hoverCls: Ext.baseCSSPrefix + 'treelist-item-over',
    rowHoverCls: Ext.baseCSSPrefix + 'treelist-row-over',
 
    /**
     * This property is `true` to allow type checking for this or derived class.
     * @property {Boolean} isTreeListItem
     * @readonly
     */
    isTreeListItem: true,
 
    config: {
        /**
         * @cfg {String} rowCls
         * One or more CSS classes to add to the tree item's logical "row" element. This
         * element fills from left-to-right of the line.
         * @since 6.0.1
         */
        rowCls: null
    },
 
    /**
     * @cfg {String} [rowClsProperty="rowCls"]
     * The name of the associated record's field to map to the {@link #rowCls} config.
     * @since 6.0.1
     */
    rowClsProperty: 'rowCls',
 
    element: {
        reference: 'element',
        tag: 'li',
        cls: Ext.baseCSSPrefix + 'treelist-item',
 
        children: [{
            reference: 'rowElement',
            cls: Ext.baseCSSPrefix + 'treelist-row',
 
            children: [{
                reference: 'wrapElement',
                cls: Ext.baseCSSPrefix + 'treelist-item-wrap',
                children: [{
                    reference: 'iconElement',
                    cls: Ext.baseCSSPrefix + 'treelist-item-icon'
                }, {
                    reference: 'textElement',
                    cls: Ext.baseCSSPrefix + 'treelist-item-text'
                }, {
                    reference: 'expanderElement',
                    cls: Ext.baseCSSPrefix + 'treelist-item-expander'
                }]
            }]
        }, {
            reference: 'itemContainer',
            tag: 'ul',
            cls: Ext.baseCSSPrefix + 'treelist-container'
        }, {
            reference: 'toolElement',
            cls: Ext.baseCSSPrefix + 'treelist-item-tool'
        }]
    },
 
    constructor: function(config) {
        var toolDom;
        
        this.callParent([config]);
 
        toolDom = this.toolElement.dom;
 
        // We don't want the tool in the normal <li> structure but it is simpler to let
        // that process create the toolElement.
        toolDom.parentNode.removeChild(toolDom);
    },
 
    getToolElement: function() {
        return this.toolElement;
    },
 
    insertItem: function(item, refItem) {
        if (refItem) {
            item.element.insertBefore(refItem.element);
        }
        else {
            this.itemContainer.appendChild(item.element);
        }
    },
 
    isSelectionEvent: function(e) {
        var owner = this.getOwner();
        
        return (!this.isToggleEvent(e) || !owner.getExpanderOnly() || owner.getSelectOnExpander());
    },
 
    isToggleEvent: function(e) {
        var isExpand = false;
 
        if (this.getOwner().getExpanderOnly()) {
            isExpand = e.target === this.expanderElement.dom;
        }
        else {
            // contains also includes the element itself
            isExpand = !this.itemContainer.contains(e.target);
        }
 
        return isExpand;
    },
 
    nodeCollapseBegin: function(animation, collapsingForExpand) {
        var me = this,
            itemContainer = me.itemContainer,
            height;
 
        if (me.expanding) {
            me.stopAnimation(me.expanding); // also calls the nodeExpandDone method
        }
 
        // Measure before collapse since that hides the element (if animating) but after
        // ending any in progress expand animation.
        height = animation && itemContainer.getHeight();
 
        me.callParent([ animation, collapsingForExpand ]);
 
        if (animation) {
            // The collapsed state is now in effect, so itemContainer is hidden.
            itemContainer.dom.style.display = 'block';
 
            me.collapsingForExpand = collapsingForExpand;
            me.collapsing = this.runAnimation(Ext.merge({
                from: {
                    height: height
                },
                to: {
                    height: 0
                },
                callback: me.nodeCollapseDone,
                scope: me
            }, animation));
        }
    },
 
    nodeCollapseDone: function(animation) {
        var me = this,
            itemContainer = me.itemContainer;
 
        // stopAnimation is called on destroy, so don't
        // bother continuing if we don't need to
        if (!me.destroying && !me.destroyed) {
            me.collapsing = null;
            itemContainer.dom.style.display = '';
            itemContainer.setHeight(null);
 
            me.nodeCollapseEnd(me.collapsingForExpand);
        }
    },
 
    nodeExpandBegin: function(animation) {
        var me = this,
            itemContainer = me.itemContainer,
            height;
 
        if (me.collapsing) {
            me.stopAnimation(me.collapsing);
        }
 
        me.callParent([ animation ]);
 
        if (animation) {
            // The expanded state is in effect, so itemContainer is visible again.
            height = itemContainer.getHeight();
            itemContainer.setHeight(0);
 
            me.expanding = me.runAnimation(Ext.merge({
                to: {
                    height: height
                },
                callback: me.nodeExpandDone,
                scope: me
            }, animation));
        }
    },
 
    nodeExpandDone: function() {
        this.expanding = null;
        this.itemContainer.setHeight(null);
        this.nodeExpandEnd();
    },
 
    removeItem: function(item) {
        this.itemContainer.removeChild(item.element);
    },
 
    //-------------------------------------------------------------------------
    // Updaters
 
    updateNode: function(node, oldNode) {
        this.syncIndent();
        this.callParent([ node, oldNode ]);
    },
 
    updateExpandable: function(expandable) {
        this.updateExpandCls();
 
        // We need not to set the expandable attribute of node here, 
        // Refer to isExapndable() function of the node. 
        // This function may get called on removal of child, and thus setting expandable to false
        // But we may not need to set same to node as isExapndable() will be deciding function 
        // not the 'Exapndable' attribute. Fase 'Exapndable' attribute means node will never
        // be expandable irrespective of the child values
    },
 
    updateExpanded: function(expanded) {
        var node = this.getNode();
 
        this.updateExpandCls();
 
        if (node) {
            node.set('expanded', expanded);
        }
    },
 
    updateIconCls: function(iconCls, oldIconCls) {
        var me = this,
            el = me.element;
 
        me.doIconCls(me.iconElement, iconCls, oldIconCls);
        me.doIconCls(me.toolElement, iconCls, oldIconCls);
 
        el.toggleCls(me.withIconCls, !!iconCls);
        // Blank iconCls leaves room for icon to line up w/sibling items
        el.toggleCls(me.hideIconCls, iconCls === null);
    },
 
    updateLeaf: function(leaf) {
        this.element.toggleCls(this.leafCls, leaf);
    },
 
    updateLoading: function(loading) {
        this.element.toggleCls(this.loadingCls, loading);
    },
 
    updateOver: function(over) {
        var me = this;
 
        me.element.toggleCls(me.hoverCls, !! over); // off if over==0, on otherwise
        me.rowElement.toggleCls(me.rowHoverCls, over > 1); // off if over = 0 or 1
    },
 
    updateRowCls: function(value, oldValue) {
        this.rowElement.replaceCls(oldValue, value);
    },
 
    updateSelected: function(selected, oldSelected) {
        var me = this,
            cls = me.selectedCls,
            tool = me.getToolElement();
 
        me.callParent([ selected, oldSelected ]);
 
        me.element.toggleCls(cls, selected);
 
        if (tool) {
            tool.toggleCls(cls, selected);
        }
    },
 
    updateSelectedParent: function(selectedParent) {
        var me = this,
            tool;
        
        me.element.toggleCls(me.selectedParentCls, selectedParent);
        tool = me.getToolElement();
        
        if (tool) {
            tool.toggleCls(me.selectedCls, selectedParent);
        }
    },
 
    updateText: function(text) {
        this.textElement.update(text);
    },
 
    //-------------------------------------------------------------------------
    // Private
 
    privates: {
        doNodeUpdate: function(node) {
            this.callParent([ node ]);
 
            this.setRowCls(node && node.data[this.rowClsProperty]);
        },
 
        doIconCls: function(element, iconCls, oldIconCls) {
            if (oldIconCls) {
                element.removeCls(oldIconCls);
            }
 
            if (iconCls) {
                element.addCls(iconCls);
            }
        },
 
        syncIndent: function() {
            var me = this,
                indent = me.getIndent(),
                node = me.getNode(),
                depth;
 
            if (node) {
                depth = node.data.depth - 1;
 
                me.wrapElement.dom.style.marginLeft = (depth * indent) + 'px';
            }
        },
 
        updateExpandCls: function() {
            if (!this.updatingExpandCls) {
                // eslint-disable-next-line vars-on-top
                var me = this,
                    expandable = me.getExpandable(),
                    element = me.element,
                    expanded = me.getExpanded(),
                    expandedCls = me.expandedCls,
                    collapsedCls = me.collapsedCls;
 
                me.updatingExpandCls = true;
 
                element.toggleCls(me.expandableCls, expandable);
 
                if (expandable) {
                    element.toggleCls(expandedCls, expanded);
                    element.toggleCls(collapsedCls, !expanded);
                }
                else {
                    element.removeCls([expandedCls, collapsedCls]);
                }
 
                me.updatingExpandCls = false;
            }
        },
 
        updateIndent: function(value, oldValue) {
            this.syncIndent();
            this.callParent([ value, oldValue ]);
        }
    }
}, function(TreeItem) {
    TreeItem.prototype.floatedCls = [
        Ext.Widget.prototype.floatedCls,
        Ext.baseCSSPrefix + 'treelist-item-floated'
    ];
});