/**
 * 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 ]);
        }
    }
});
