/**
 * 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',
    floatedCls: Ext.baseCSSPrefix + 'treelist-item-floated',
    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) {
        this.callParent([config]);
 
        var 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;
 
        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 () {
        this.updateExpandCls();
    },
 
    updateExpanded: function () {
        this.updateExpandCls();
    },
 
    updateFloated: function (floated, wasFloated) {
        var me = this;
 
        me.callParent([floated, wasFloated]);
 
        me.element.toggleCls(me.floatedCls, floated);
        me.toolElement.toggleCls(me.floatedToolCls, floated);
    },
 
    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);
        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;
        
        me.element.toggleCls(me.selectedParentCls, selectedParent);
        var 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) {
                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 ]);
        }
    }
});