/**
 * Headercontainer is a docked container (_`top` or `bottom` only_) that holds the
 * headers ({@link Ext.grid.column.Column grid columns}) of a
 * {@link Ext.grid.Panel grid} or {@link Ext.tree.Panel tree}.  The headercontainer
 * handles resizing, moving, and hiding columns.  As columns are hidden, moved or
 * resized, the headercontainer triggers changes within the grid or tree's
 * {@link Ext.view.Table view}.  You will not generally need to instantiate this class
 * directly.
 *
 * You may use the
 * {@link Ext.panel.Table#method-getHeaderContainer getHeaderContainer()}
 * accessor method to access the tree or grid's headercontainer.
 *
 * Grids and trees also have an alias to the two more useful headercontainer methods:
 *
 *  - **{@link Ext.panel.Table#method-getColumns getColumns}** - aliases
 * {@link Ext.grid.header.Container#getGridColumns}
 *  - **{@link Ext.panel.Table#method-getVisibleColumns getVisibleColumns}** - aliases
 * {@link Ext.grid.header.Container#getVisibleGridColumns}
 */
Ext.define('Ext.grid.header.Container', {
    extend: 'Ext.container.Container',
    alias: 'widget.headercontainer',
 
    requires: [
        'Ext.grid.ColumnLayout',
        'Ext.grid.plugin.HeaderResizer',
        'Ext.grid.plugin.HeaderReorderer',
        'Ext.util.KeyNav'
    ],
 
    uses: [
        'Ext.grid.column.Column',
        'Ext.grid.ColumnManager',
        'Ext.menu.Menu',
        'Ext.menu.CheckItem',
        'Ext.menu.Separator'
    ],
 
    border: true,
 
    baseCls: Ext.baseCSSPrefix + 'grid-header-ct',
 
    dock: 'top',
 
    /**
     * @cfg {Number} weight
     * HeaderContainer overrides the default weight of 0 for all docked items to 100.
     * This is so that it has more priority over things like toolbars.
     */
    weight: 100,
 
    defaultType: 'gridcolumn',
 
    /**
     * @cfg {Number} defaultWidth
     * Width of the header if no width or flex is specified.
     */
    defaultWidth: 100,
 
    /**
     * @cfg {Boolean} [sealed=false]
     * Specify as `true` to constrain column dragging so that a column cannot be dragged into
     * or out of this column.
     *
     * **Note that this config is only valid for column headers which contain child column headers,
     * eg:**
     *     {
     *         sealed: true
     *         text: 'ExtJS',
     *         columns: [{
     *             text: '3.0.4',
     *             dataIndex: 'ext304'
     *         }, {
     *             text: '4.1.0',
     *             dataIndex: 'ext410'
     *         }
     *     }
     *
     */
 
    /**
     * @cfg {String} sortAscText
     * The text to display in the sort menu to sort items in ascending order.
     * @locale
     */
    sortAscText: 'Sort Ascending',
 
    /**
     * @cfg {String} sortDescText
     * The text to display in the sort menu to sort items in descending order.
     * @locale
     */
    sortDescText: 'Sort Descending',
 
    /**
     * @cfg {String} sortClearText
     * The text to display in the sort menu to clear the sort order.
     * @locale
     */
    sortClearText: 'Clear Sort',
 
    /**
     * @cfg {String} columnsText
     * The text for the columns submenu item.
     * @locale
     */
    columnsText: 'Columns',
 
    headerOpenCls: Ext.baseCSSPrefix + 'column-header-open',
 
    menuSortAscCls: Ext.baseCSSPrefix + 'hmenu-sort-asc',
 
    menuSortDescCls: Ext.baseCSSPrefix + 'hmenu-sort-desc',
 
    menuColsIcon: Ext.baseCSSPrefix + 'cols-icon',
 
    blockEvents: false,
 
    dragging: false,
 
    // May be set to false by a SptreadSheetSelectionModel
    sortOnClick: true,
 
    // Disable FocusableContainer behavior by default, since we only want it
    // to be enabled for the root header container (we'll set the flag in initComponent)
    focusableContainer: false,
 
    childHideCount: 0,
 
    /**
     * @property {Boolean} isGroupHeader
     * True if this HeaderContainer is in fact a group header which contains sub headers.
     */
 
    /**
     * @cfg {Boolean} sortable
     * Provides the default sortable state for all Headers within this HeaderContainer.
     * Also turns on or off the menus in the HeaderContainer. Note that the menu is
     * shared across every header and therefore turning it off will remove the menu
     * items for every header.
     */
    sortable: true,
 
    /**
     * @cfg {Boolean} [enableColumnHide=true]
     * False to disable column hiding within this grid.
     */
    enableColumnHide: true,
 
    /**
     * @event columnresize
     * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates
     * all column headers.
     * @param {Ext.grid.column.Column} column The Column header Component which provides
     * the column definition
     * @param {Number} width 
     */
 
    /**
     * @event headerclick
     * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates
     * all column headers.
     * @param {Ext.grid.column.Column} column The Column header Component which provides
     * the column definition
     * @param {Ext.event.Event} e 
     * @param {HTMLElement} t 
     */
 
    /**
     * @event headercontextmenu
     * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates
     * all column headers.
     * @param {Ext.grid.column.Column} column The Column header Component which provides
     * the column definition
     * @param {Ext.event.Event} e 
     * @param {HTMLElement} t 
     */
 
    /**
     * @event headertriggerclick
     * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates
     * all column headers.
     * @param {Ext.grid.column.Column} column The Column header Component which provides
     * the column definition
     * @param {Ext.event.Event} e 
     * @param {HTMLElement} t 
     */
 
    /**
     * @event columnmove
     * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates
     * all column headers.
     * @param {Ext.grid.column.Column} column The Column header Component which provides
     * the column definition
     * @param {Number} fromIdx 
     * @param {Number} toIdx 
     */
 
    /**
     * @event columnhide
     * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates
     * all column headers.
     * @param {Ext.grid.column.Column} column The Column header Component which provides
     * the column definition
     */
 
    /**
     * @event columnshow
     * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates
     * all column headers.
     * @param {Ext.grid.column.Column} column The Column header Component which provides
     * the column definition
     */
 
    /**
     * @event columnschanged
     * Fired after the columns change in any way, when a column has been hidden or shown,
     * or when a column is added to or removed from this header container.
     * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates
     * all column headers.
     */
 
    /**
     * @event sortchange
     * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates
     * all column headers.
     * @param {Ext.grid.column.Column} column The Column header Component which provides
     * the column definition
     * @param {String} direction 
     */
 
    /**
     * @event menucreate
     * Fired immediately after the column header menu is created.
     * @param {Ext.grid.header.Container} ct This instance
     * @param {Ext.menu.Menu} menu The Menu that was created
     */
 
    /**
     * @event headermenucreate
     * Fired immediately after the column header menu is created.
     * @param {Ext.panel.Table} grid This grid instance
     * @param {Ext.menu.Menu} menu The Menu that was created
     * @param {Ext.grid.header.Container} headerCt This header container
     * @member Ext.panel.Table
     */
 
    initComponent: function() {
        var me = this;
 
        me.plugins = me.plugins || [];
        me.defaults = me.defaults || {};
 
        // TODO: Pass in configurations to turn on/off dynamic
        //       resizing and disable resizing all together
 
        // Only set up a Resizer and Reorderer for the root HeaderContainer.
        // Nested Group Headers are themselves HeaderContainers
        if (!me.isColumn) {
            me.isRootHeader = true;
 
            if (me.enableColumnResize) {
                me.resizer = new Ext.grid.plugin.HeaderResizer();
                me.plugins.push(me.resizer);
            }
 
            if (me.enableColumnMove) {
                me.reorderer = new Ext.grid.plugin.HeaderReorderer();
                me.plugins.push(me.reorderer);
            }
        }
 
        // If this is a leaf column header, and is NOT functioning as a container,
        // use Container layout with a no-op calculate method.
        if (me.isColumn && !me.isGroupHeader) {
            if (!me.items || !me.items.length) {
                me.isContainer = me.isFocusableContainer = false;
 
                me.layout = {
                    type: 'container',
                    calculate: Ext.emptyFn
                };
            }
 
            // Allow overriding via instance config
            if (!me.hasOwnProperty('focusable')) {
                // It is a bit odd making a container focusable, but column headers must
                // be focusable or else all kinds of problems arise. For starters, one
                // cannot click on it to sort unless the first column is visible because
                // the click will attempt to focus the header, but not being focusable, the
                // search for a focus target will bubble up to the header container and
                // back down to the first focusable header. If that causes a scroll, the
                // original column header that was clicked can scroll out of view and
                // effectively veto the click/sort. Secondarily, if the header is not
                // focusable, one cannot navigate to it with arrow keys as you would do
                // normally. Granted, the addition of components inside the header makes
                // for an odd (and non-standard) experience. The alternative, however, is
                // totally unacceptable. Perhaps there is a way to someday "blend" such
                // focusable container items into the parent container, but until research
                // into that approach is done, this is the best we can do.
                me.focusable = true;
            }
        }
        // HeaderContainer and Group header needs a gridcolumn layout.
        else {
            me.layout = Ext.apply({
                type: 'gridcolumn',
                align: 'stretch'
            }, me.initialConfig.layout);
 
            // All HeaderContainers need to know this so that leaf Columns can adjust
            // for cell border width if using content box model
            me.defaults.columnLines = me.columnLines;
 
            // Initialize the root header.
            if (me.isRootHeader) {
 
                // The root header is a FocusableContainer if it's not carrying hidden headers.
                if (!me.hiddenHeaders) {
                    me.focusableContainer = true;
                    me.ariaRole = 'rowgroup';
                }
 
                // Create column managers for the root header.
                me.columnManager = new Ext.grid.ColumnManager(false, me);
                me.visibleColumnManager = new Ext.grid.ColumnManager(true, me);
 
                // In the grid config, if grid.columns is a header container instance
                // and not a columns config, then it currently has no knowledge
                // of a containing grid. Create the column manager now and bind it to the grid
                // later in Ext.panel.Table:initComponent().
                //
                // In most cases, though, grid.columns will be a config, so the grid
                // is already known and the column manager can be bound to it.
                if (me.grid) {
                    me.grid.columnManager = me.columnManager;
                    me.grid.visibleColumnManager = me.visibleColumnManager;
                }
            }
            else {
                // Is a group header, also create column managers.
                me.visibleColumnManager = new Ext.grid.ColumnManager(true, me);
                me.columnManager = new Ext.grid.ColumnManager(false, me);
            }
        }
 
        me.menuTask = new Ext.util.DelayedTask(me.updateMenuDisabledState, me);
        me.callParent();
    },
 
    isNested: function() {
        return !!this.getRootHeaderCt().down('[isNestedParent]');
    },
 
    isNestedGroupHeader: function() {
        // The owner only has one item that isn't hidden and it's me; hide the owner.
        var header = this,
            items = header.getRefOwner().query('>:not([hidden])');
 
        return (items.length === 1 && items[0] === header);
    },
 
    /**
     * Returns the column's sealed status.
     *
     * @return {Boolean} `true` if this column is sealed, `false` otherwise.
     *
     * @since 6.5.0
     */
    isSealed: function() {
        return !!(this.sealed || this.getInherited().sealed);
    },
 
    maybeShowNestedGroupHeader: function() {
        // Group headers are special in that they are auto-hidden when their subheaders are all
        // hidden and auto-shown when the first subheader is reshown. They are the only headers
        // that should now be auto-shown or -hidden.
        //
        // It follows that since group headers are dictated by some automation depending upon the
        // state of their child items that all group headers should be shown if anyone in the
        // hierarchy is shown since these special group headers only contain one child, which is
        // the next group header in the stack.
        // This only should apply to the following grouped header scenario:
        //
        //           +-----------------------------------+
        //           |               Group 1             |
        //           |-----------------------------------|
        //           |               Group 2             |
        //   other   |-----------------------------------|   other
        //  headers  |               Group 3             |  headers
        //           |-----------------------------------|
        //           | Field3 | Field4 | Field5 | Field6 |
        //           |===================================|
        //           |               view                |
        //           +-----------------------------------+
        //
        var items = this.items,
            item;
 
        if (items && items.length === 1 && (item = items.getAt(0)) && item.hidden) {
            item.show();
        }
    },
 
    setNestedParent: function(target) {
        // Here we need to prevent the removal of ancestor group headers from occuring
        // if a flag is set. This is needed when there are stacked group headers
        // and only the deepest nested group header has leaf items in its collection.
        // In this specific scenario, the group headers above it only have 1 item, which is its
        // child nested group header.
        //
        // If we don't set this flag, then all of the grouped headers will be recursively removed
        // all the way up to the root container b/c Ext.grid.header.Container#onRemove will remove
        // all containers that don't contain any items.
        //
        // Note that if an ownerCt only has one item, then we know that this item
        // is the group header that we're currently dragging.
        //
        // Also, note that we mark the owner as the target header because everything up to that
        // should be removed.
        //
        // We have to reset any previous headers that may have been target.ownerCts!
        target.isNestedParent = false;
        target.ownerCt.isNestedParent =
            !!(this.ownerCt.items.length === 1 && target.ownerCt.items.length === 1);
    },
 
    initEvents: function() {
        var me = this,
            onHeaderCtEvent,
            listeners;
 
        me.callParent();
 
        // If this is top level, listen for events to delegate to descendant headers.
        if (!me.isColumn && !me.isGroupHeader) {
            onHeaderCtEvent = me.onHeaderCtEvent;
            listeners = {
                click: onHeaderCtEvent,
                dblclick: onHeaderCtEvent,
                contextmenu: onHeaderCtEvent,
                mousedown: me.onHeaderCtMouseDown,
                mouseover: me.onHeaderCtMouseOver,
                mouseout: me.onHeaderCtMouseOut,
                scope: me
            };
 
            if (Ext.supports.Touch) {
                listeners.longpress = me.onHeaderCtLongPress;
            }
 
            me.mon(me.el, listeners);
        }
    },
 
    onHeaderCtEvent: function(e, t) {
        var me = this,
            headerEl = me.getHeaderElByEvent(e),
            header,
            targetEl,
            activeHeader;
 
        if (me.longPressFired) {
            // if we just showed the menu as a result of a longpress, do not process
            // the click event and sort the column.
            me.longPressFired = false;
 
            return;
        }
 
        if (headerEl && !me.blockEvents) {
            header = Ext.getCmp(headerEl.id);
 
            if (header) {
                targetEl = header[header.clickTargetName];
 
                // If there's no possibility that the mouseEvent was on child header items,
                // or it was definitely in our titleEl, then process it
                if ((!header.isGroupHeader && !header.isContainer) || e.within(targetEl)) {
                    if (e.type === 'click' || e.type === 'tap') {
                        // The header decides which header to activate on click
                        // on Touch, anywhere in the splitter zone activates
                        // the left header.
                        activeHeader = header.onTitleElClick(e, targetEl, me.sortOnClick);
 
                        if (activeHeader) {
                            // If activated by touch, there is no trigger el to align with,
                            // so align to the header element.
                            me.onHeaderTriggerClick(
                                activeHeader, e,
                                e.pointerType === 'touch' ? activeHeader.el : activeHeader.triggerEl
                            );
                        }
                        else {
                            me.onHeaderClick(header, e, t);
                        }
                    }
                    else if (e.type === 'contextmenu') {
                        me.onHeaderContextMenu(header, e, t);
                    }
                    else if (e.type === 'dblclick') {
                        header.onTitleElDblClick(e, targetEl.dom);
                    }
                }
            }
        }
    },
 
    blockNextEvent: function() {
        var me = this;
 
        me.blockEvents = true;
 
        if (!me.unblockTimer) {
            me.unblockTimer = Ext.asap(me.unblockEvents, me);
        }
    },
 
    unblockEvents: function() {
        this.blockEvents = this.unblockTimer = false;
    },
 
    onHeaderCtMouseDown: function(e, target) {
        var targetCmp = Ext.Component.from(target),
            cols, i, len, scrollable, col;
 
        if (!e.defaultPrevented && targetCmp !== this) {
            // The DDManager (Header Containers are draggable) prevents mousedown default
            // So we must explicitly focus the header
            if (targetCmp.isGroupHeader) {
                cols = targetCmp.getVisibleGridColumns();
                scrollable = this.getScrollable();
 
                for (= 0, len = cols.length; i < len; ++i) {
                    col = cols[i];
 
                    if (scrollable.doIsInView(col.el, true).x) {
                        targetCmp = col;
                        break;
                    }
                }
            }
 
            targetCmp.focus();
        }
    },
 
    onHeaderCtMouseOver: function(e, t) {
        var headerEl,
            header,
            targetEl;
 
        // Only proces the mouse entering this HeaderContainer.
        // From header to header, and exiting this HeaderContainer we track using mouseout events.
        if (!e.within(this.el, true)) {
            headerEl = e.getTarget('.' + Ext.grid.column.Column.prototype.baseCls);
            header = headerEl && Ext.getCmp(headerEl.id);
 
            if (header) {
                targetEl = header[header.clickTargetName];
 
                if (e.within(targetEl)) {
                    header.onTitleMouseOver(e, targetEl.dom);
                }
            }
        }
    },
 
    onHeaderCtMouseOut: function(e, t) {
        var headerSelector = '.' + Ext.grid.column.Column.prototype.baseCls,
            outHeaderEl = e.getTarget(headerSelector),
            inHeaderEl = e.getRelatedTarget(headerSelector),
            header,
            targetEl;
 
        // It's a mouseenter/leave, not an internal element change within a Header
        if (outHeaderEl !== inHeaderEl) {
            if (outHeaderEl) {
                header = Ext.getCmp(outHeaderEl.id);
 
                if (header) {
                    targetEl = header[header.clickTargetName];
                    header.onTitleMouseOut(e, targetEl.dom);
                }
            }
 
            if (inHeaderEl) {
                header = Ext.getCmp(inHeaderEl.id);
 
                if (header) {
                    targetEl = header[header.clickTargetName];
                    header.onTitleMouseOver(e, targetEl.dom);
                }
            }
        }
    },
 
    onHeaderCtLongPress: function(e) {
        var me = this,
            headerEl = me.getHeaderElByEvent(e),
            header;
 
        // Might be outside the headers.
        if (headerEl) {
            header = Ext.getCmp(headerEl.id);
 
            if (header && !header.menuDisabled) {
                me.longPressFired = true;
                me.showMenuBy(e, headerEl, header);
            }
        }
    },
 
    getHeaderElByEvent: function(e) {
        return e.getTarget('.' + Ext.grid.column.Column.prototype.baseCls);
    },
 
    isLayoutRoot: function() {
        // Since we're docked, the width is always calculated
        // If we're hidden, the height is explicitly 0, which
        // means we'll be considered a layout root. However, we
        // still need the view to layout to update the underlying
        // table to match the size.
        if (this.hiddenHeaders) {
            return false;
        }
 
        return this.callParent();
    },
 
    // Find the topmost HeaderContainer
    getRootHeaderCt: function() {
        return this.isRootHeader ? this : this.up('[isRootHeader]');
    },
 
    doDestroy: function() {
        var me = this;
 
        if (me.menu) {
            me.menu.un('hide', me.onMenuHide, me);
        }
 
        Ext.unasap(me.unblockTimer);
        me.menuTask.cancel();
 
        Ext.destroy(me.visibleColumnManager, me.columnManager, me.menu);
 
        me.callParent();
    },
 
    removeAll: function(autoDestroy) {
        var me = this;
 
        // fire a single columnschanged event after all removes have been made
        me.suspendEvent('columnschanged');
        me.callParent([autoDestroy]);
        me.resumeEvent('columnschanged');
        me.fireEvent('columnschanged', me);
    },
 
    applyColumnsState: function(columnsState, storeState) {
        if (!columnsState) {
            return;
        }
 
        // eslint-disable-next-line vars-on-top
        var me = this,
            items = me.items.items,
            count = items.length,
            i = 0,
            moved = false,
            newOrder = [],
            newCols = [],
            length, col, columnState, index;
 
        me.purgeCache();
 
        for (= 0; i < count; i++) {
            col = items[i];
            columnState = columnsState[col.getStateId()];
 
            // There's a column state for this column.
            // Add it to the newOrder array at the specified index
            if (columnState) {
                index = columnState.index;
                newOrder[index] = col;
 
                if (!== index) {
                    moved = true;
                }
 
                if (col.applyColumnState) {
                    col.applyColumnState(columnState, storeState);
                }
            }
            // A new column.
            // It must be inserted at this index after state restoration,
            else {
                newCols.push({
                    index: i,
                    column: col
                });
            }
        }
 
        // If any saved columns were missing, close the gaps where they were
        newOrder = Ext.Array.clean(newOrder);
 
        // New column encountered.
        // Insert them into the newOrder at their configured position
        length = newCols.length;
 
        if (length) {
            for (= 0; i < length; i++) {
                columnState = newCols[i];
                index = columnState.index;
 
                if (index < newOrder.length) {
                    moved = true;
                    Ext.Array.splice(newOrder, index, 0, columnState.column);
                }
                else {
                    newOrder.push(columnState.column);
                }
            }
        }
 
        if (moved) {
            // This flag will prevent the groupheader from being removed by its owner
            // when it (temporarily) has no child items.
            me.applyingState = true;
            me.removeAll(false);
            delete me.applyingState;
 
            me.add(newOrder);
        }
    },
 
    getColumnsState: function() {
        var me = this,
            columns = [],
            state;
 
        me.items.each(function(col) {
            state = col.getColumnState && col.getColumnState();
 
            if (state) {
                columns.push(state);
            }
        });
 
        return columns;
    },
 
    // Invalidate column cache on add
    // We cannot refresh the View on every add because this method is called
    // when the HeaderDropZone moves Headers around, that will also refresh the view
    onAdd: function(c) {
        var me = this,
            rootHeader // eslint-disable-line semi
 
            //<debug>
            , stateId = c.getStateId(); // eslint-disable-line comma-style
 
        if (stateId != null) {
            if (!me._usedIDs) {
                me._usedIDs = {};
            }
 
            if (me._usedIDs[stateId] && me._usedIDs[stateId] !== c) {
                Ext.log.warn(this.$className + ' attempted to reuse an existing id: ' + stateId);
            }
 
            me._usedIDs[stateId] = c;
        }
        //</debug>
 
        me.callParent(arguments);
 
        rootHeader = me.getRootHeaderCt();
        me.onHeadersChanged(c, rootHeader && rootHeader.isDDMoveInGrid);
    },
 
    move: function(fromIdx, toIdx) {
        var me = this,
            items = me.items,
            headerToMove;
 
        if (fromIdx.isComponent) {
            headerToMove = fromIdx;
            fromIdx = items.indexOf(headerToMove);
        }
        else {
            headerToMove = items.getAt(fromIdx);
        }
 
        // Take real grid column index of column being moved
        headerToMove.visibleFromIdx =
            me.getRootHeaderCt().visibleColumnManager.indexOf(headerToMove);
 
        me.callParent(arguments);
    },
 
    onMove: function(headerToMove, fromIdx, toIdx) {
        var me = this,
            gridHeaderCt = me.getRootHeaderCt(),
            gridVisibleColumnManager = gridHeaderCt.visibleColumnManager,
            numColsToMove = 1,
            visibleToIdx;
 
        // Purges cache so that indexOf returns new position of header
        me.onHeadersChanged(headerToMove, true);
 
        visibleToIdx = gridVisibleColumnManager.indexOf(headerToMove);
 
        if (visibleToIdx >= headerToMove.visibleFromIdx) {
            visibleToIdx++;
        }
 
        me.callParent(arguments);
 
        // If what is being moved is a group header, then pass the correct column count
        if (headerToMove.isGroupHeader) {
            numColsToMove = headerToMove.visibleColumnManager.getColumns().length;
        }
 
        gridHeaderCt.onHeaderMoved(headerToMove, numColsToMove, headerToMove.visibleFromIdx,
                                   visibleToIdx);
    },
 
    // @private
    maybeContinueRemove: function() {
        var me = this;
 
        // Note that if the column is a group header and is the current target of a drag,
        // we don't want to remove it if it since it could be one of any number of (empty)
        // nested group headers. See #isNested.
        //
        // There are also other scenarios in which the remove should not occur. For instance,
        // when applying column state to a groupheader, the subheaders are all removed
        // before being re-added in their stateful order, and the groupheader should not be removed
        // in the meantime.
        // See EXTJS-17577.
        return (me.isGroupHeader && !me.applyingState) && !me.isNestedParent && me.ownerCt &&
               !me.items.getCount();
    },
 
    // Invalidate column cache on remove
    // We cannot refresh the View on every remove because this method is called
    // when the HeaderDropZone moves Headers around, that will also refresh the view
    onRemove: function(c, isDestroying) {
        var me = this,
            ownerCt = me.ownerCt;
 
        me.callParent([c, isDestroying]);
 
        //<debug>
        if (!me._usedIDs) {
            me._usedIDs = {};
        }
 
        delete me._usedIDs[c.headerId];
        //</debug>
 
        if (!me.destroying) {
            // isDDMoveInGrid flag set by Ext.grid.header.DropZone when moving into another
            // container *within the same grid*. This stops header change processing
            // from being executed twice, once on remove and then on the subsequent add.
            if (!me.getRootHeaderCt().isDDMoveInGrid) {
                me.onHeadersChanged(c, false);
            }
 
            if (me.maybeContinueRemove()) {
                // Detach the header from the DOM here. Since we're removing and destroying
                // the container, the inner DOM may get overwritten,
                // since Container#detachOnRemove gets processed after onRemove.
                if (c.rendered) {
                    c.detachFromBody();
                }
 
                // If we don't have any items left and we're a group, remove ourselves.
                // This will cascade up if necessary. DO NOT destroy ourselves here,
                // we have to defer that until all moves are done and events are fired.
                me.destroyAfterRemoving = true;
 
                Ext.suspendLayouts();
                ownerCt.remove(me, false);
                Ext.resumeLayouts(true);
            }
        }
    },
 
    // Private
    // Called to clear all caches of columns whenever columns are added, removed to just moved.
    // We need to be informed if it's just a move operation so that we don't call the heavier
    // grid.onHeadersChanged which refreshes the view.
    // The onMove handler ensures that grid.inHeaderMove is called which just swaps cells.
    onHeadersChanged: function(c, isMove) {
        var gridPanel,
            gridHeaderCt = this.getRootHeaderCt();
 
        // Each HeaderContainer up the chain must have its cache purged so that its getGridColumns
        // method will return correct results.
        this.purgeHeaderCtCache(this);
 
        if (gridHeaderCt) {
            gridHeaderCt.onColumnsChanged();
            gridPanel = gridHeaderCt.ownerCt;
 
            // The grid needs to be informed even if the added/removed column is a group header
            // If it an add or remove operation causing this header change call, then inform
            // the grid which refreshes.
            // Moving calls the onHeaderMoved method of the grid which just swaps cells.
            if (gridPanel && !isMove) {
                gridPanel.onHeadersChanged(gridHeaderCt, c);
            }
        }
    },
 
    // Private
    onHeaderMoved: function(header, colsToMove, fromIdx, toIdx) {
        var me = this,
            gridSection = me.ownerCt;
 
        if (me.rendered) {
            if (gridSection && gridSection.onHeaderMove) {
                gridSection.onHeaderMove(me, header, colsToMove, fromIdx, toIdx);
            }
 
            me.fireEvent('columnmove', me, header, fromIdx, toIdx);
        }
    },
 
    // Private
    // Only called on the grid's headerCt.
    // Called whenever a column is added or removed or moved at any level below.
    // Ensures that the gridColumns caches are cleared.
    onColumnsChanged: function() {
        var me = this,
            menu = me.menu,
            columnItemSeparator,
            columnItem;
 
        if (me.rendered) {
            me.fireEvent('columnschanged', me);
 
            // Column item (and its associated menu) menu has to be destroyed (if it exits)
            // when columns are changed.
            // It will be recreated just before the main container menu is next shown.
            if (menu) {
                columnItemSeparator = menu.child('#columnItemSeparator');
                columnItem = menu.child('#columnItem');
 
                // Destroy the column visibility items
                // They will be recreated before the next show
                if (columnItemSeparator) {
                    columnItemSeparator.destroy();
                }
 
                if (columnItem) {
                    columnItem.destroy();
                }
            }
        }
    },
 
    /**
     * @private
     */
    lookupComponent: function(comp) {
        var result = this.callParent(arguments);
 
        // Apply default width unless it's a group header (in which case it must be left
        // to shrinkwrap), or it's flexed. Test whether width is undefined so that width: null
        // can be used to have the header shrinkwrap its text.
        if (!result.isGroupHeader && result.width === undefined && !result.flex) {
            result.width = this.defaultWidth;
        }
 
        return result;
    },
 
    /**
     * @private
     * Synchronize column UI visible sort state with Store's sorters.
     */
    setSortState: function() {
        var store = this.up('[store]').store,
            columns = this.visibleColumnManager.getColumns(),
            len = columns.length,
            i,
            header, sorter;
 
        for (= 0; i < len; i++) {
            header = columns[i];
 
            // Access the column's custom sorter in preference to one keyed on the data index.
            sorter = header.getSorter();
 
            if (sorter) {
                // If the column was configured with a sorter, we must check that the sorter
                // is part of the store's sorter collection to update the UI to the correct state.
                // The store may not actually BE sorted by that sorter.
                if (!store.getSorters().contains(sorter)) {
                    sorter = null;
                }
            }
            else {
                sorter = store.getSorters().get(header.getSortParam());
            }
 
            // Important: A null sorter for this column will *clear* the UI sort indicator.
            header.setSortState(sorter);
        }
    },
 
    getHeaderMenu: function() {
        var menu = this.getMenu(),
            item;
 
        if (menu) {
            item = menu.child('#columnItem');
 
            if (item) {
                return item.menu;
            }
        }
 
        return null;
    },
 
    onHeaderVisibilityChange: function(header, visible) {
        var me = this,
            menu = me.getHeaderMenu(),
            item;
 
        // Invalidate column collections upon column hide/show
        me.purgeHeaderCtCache(header.ownerCt);
 
        if (menu) {
            // If the header was hidden programmatically, sync the Menu state
            item = me.getMenuItemForHeader(menu, header);
 
            if (item) {
                item.setChecked(visible, true);
            }
 
            // delay this since the headers may fire a number of times
            // if we're hiding/showing groups
            if (menu.isVisible()) {
                me.menuTask.delay(50);
            }
        }
    },
 
    updateMenuDisabledState: function(menu) {
        var me = this,
            columns = me.query('gridcolumn:not([hidden])'),
            i,
            len = columns.length,
            item,
            checkItem,
            method;
 
        // If called from menu creation, it will be passed to avoid infinite recursion
        if (!menu) {
            menu = me.getMenu();
        }
 
        for (= 0; i < len; ++i) {
            item = columns[i];
            checkItem = me.getMenuItemForHeader(menu, item);
 
            if (checkItem) {
                method = item.isHideable() ? 'enable' : 'disable';
 
                if (checkItem.menu) {
                    method += 'CheckChange';
                }
 
                checkItem[method]();
            }
        }
    },
 
    getMenuItemForHeader: function(menu, header) {
        return header ? menu.down('menucheckitem[headerId=' + header.id + ']') : null;
    },
 
    onHeaderShow: function(header) {
        var me = this,
            ownerCt = me.ownerCt,
            lastHiddenHeader = header.lastHiddenHeader;
 
        if (!ownerCt) {
            return;
        }
 
        if (me.forceFit) {
            delete me.flex;
        }
 
        // If lastHiddenHeader exists we know that header is a groupHeader
        // and if all its subheaders are hidden then we need to show the last one that was hidden.
        if (lastHiddenHeader && !header.query('[hidden=false]').length) {
            lastHiddenHeader.show();
            header.lastHiddenHeader = null;
        }
 
        me.onHeaderVisibilityChange(header, true);
        ownerCt.onHeaderShow(me, header);
 
        me.fireEvent('columnshow', me, header);
        me.fireEvent('columnschanged', this);
    },
 
    onHeaderHide: function(header) {
        var me = this,
            ownerCt = me.ownerCt;
 
        if (!ownerCt) {
            return;
        }
 
        me.onHeaderVisibilityChange(header, false);
        ownerCt.onHeaderHide(me, header);
 
        me.fireEvent('columnhide', me, header);
        me.fireEvent('columnschanged', this);
    },
 
    onHeaderResize: function(header, w) {
        var me = this,
            gridSection = me.ownerCt;
 
        if (gridSection) {
            gridSection.onHeaderResize(me, header, w);
        }
 
        me.fireEvent('columnresize', me, header, w);
    },
 
    onHeaderClick: function(header, e, t) {
        var me = this,
            selModel = header.getView().getSelectionModel(),
            ret;
 
        header.fireEvent('headerclick', me, header, e, t);
        ret = me.fireEvent('headerclick', me, header, e, t);
 
        if (ret !== false) {
            if (selModel.onHeaderClick) {
                selModel.onHeaderClick(me, header, e);
            }
        }
 
        return ret;
    },
 
    onHeaderContextMenu: function(header, e, t) {
        header.fireEvent('headercontextmenu', this, header, e, t);
        this.fireEvent('headercontextmenu', this, header, e, t);
    },
 
    onHeaderTriggerClick: function(header, e, t) {
        var me = this;
 
        if (header.fireEvent('headertriggerclick', me, header, e, t) !== false &&
            me.fireEvent('headertriggerclick', me, header, e, t) !== false) {
            // If menu is already active...
            if (header.activeMenu) {
                // Click/tap toggles the menu visibility.
                if (e.pointerType) {
                    header.activeMenu.hide();
                }
                else {
                    header.activeMenu.focus();
                }
            }
            else {
                me.showMenuBy(e, t, header);
            }
        }
    },
 
    /**
     * @private
     *
     * Shows the column menu under the target element passed. This method is used when
     * the trigger element on the column
     * header is clicked on and rarely should be used otherwise.
     *
     * @param {Ext.event.Event} [clickEvent] The event which triggered the current handler.
     * If omitted or a key event, the menu autofocuses its first item.
     * @param {HTMLElement/Ext.dom.Element} t The target to show the menu by
     * @param {Ext.grid.header.Container} header The header container that the trigger
     * was clicked on.
     */
    showMenuBy: function(clickEvent, t, header) {
        var menu = this.getMenu(),
            ascItem = menu.down('#ascItem'),
            descItem = menu.down('#descItem'),
            sortableMth;
 
        // Use ownerCmp as the upward link. Menus *must have no ownerCt* - they are global floaters.
        // Upward navigation is done using the up() method.
        menu.activeHeader = menu.ownerCmp = header;
        header.setMenuActive(menu);
 
        // enable or disable asc & desc menu items based on header being sortable
        sortableMth = header.sortable ? 'enable' : 'disable';
 
        if (ascItem) {
            ascItem[sortableMth]();
        }
 
        if (descItem) {
            descItem[sortableMth]();
        }
 
        // Pointer-invoked menus do not auto focus, key invoked ones do.
        menu.autoFocus = !clickEvent || clickEvent.keyCode;
 
        // For longpress t is the header, for click/hover t is the trigger
        menu.showBy(t, 'tl-bl?');
 
        // Menu show was vetoed by event handler - clear context
        if (!menu.isVisible()) {
            this.onMenuHide(menu);
        }
    },
 
    hideMenu: function() {
        if (this.menu) {
            this.menu.hide();
        }
    },
 
    // remove the trigger open class when the menu is hidden
    onMenuHide: function(menu) {
        menu.activeHeader.setMenuActive(false);
    },
 
    purgeHeaderCtCache: function(headerCt) {
        while (headerCt) {
            headerCt.purgeCache();
 
            if (headerCt.isRootHeader) {
                return;
            }
 
            headerCt = headerCt.ownerCt;
        }
    },
 
    purgeCache: function() {
        var me = this,
            visibleColumnManager = me.visibleColumnManager,
            columnManager = me.columnManager;
 
        // Delete column cache - column order has changed.
        me.gridVisibleColumns = me.gridDataColumns = me.hideableColumns = null;
 
        // ColumnManager. Only the top
        if (visibleColumnManager) {
            visibleColumnManager.invalidate();
            columnManager.invalidate();
        }
    },
 
    /**
     * Gets the menu (and will create it if it doesn't already exist)
     * @private
     */
    getMenu: function() {
        var me = this,
            grid = me.view && me.view.ownerGrid;
 
        if (!me.menu) {
            me.menu = new Ext.menu.Menu({
                hideOnParentHide: false,  // Persists when owning ColumnHeader is hidden
                items: me.getMenuItems(),
                listeners: {
                    beforeshow: me.beforeMenuShow,
                    hide: me.onMenuHide,
                    scope: me
                }
            });
            me.fireEvent('menucreate', me, me.menu);
 
            if (grid) {
                grid.fireEvent('headermenucreate', grid, me.menu, me);
            }
        }
 
        return me.menu;
    },
 
    // Render our menus to the first enclosing scrolling element so that they scroll with the grid
    beforeMenuShow: function(menu) {
        var me = this,
            columnItem = menu.child('#columnItem'),
            hideableColumns,
            insertPoint;
 
        // If a change of column structure caused destruction of the column menu item
        // or the main menu was created without the column menu item because it began
        // with no hideable headers. Then create it and its menu now.
        if (!columnItem) {
            hideableColumns = me.enableColumnHide ? me.getColumnMenu(me) : null;
 
            // Insert after the "Sort Ascending", "Sort Descending" menu items if they are present.
            insertPoint = me.sortable ? 2 : 0;
 
            if (hideableColumns && hideableColumns.length) {
                menu.insert(insertPoint, [{
                    itemId: 'columnItemSeparator',
                    xtype: 'menuseparator'
                }, {
                    itemId: 'columnItem',
                    text: me.columnsText,
                    iconCls: me.menuColsIcon,
                    menu: {
                        items: hideableColumns
                    },
                    hideOnClick: false
                }]);
            }
        }
 
        me.updateMenuDisabledState(me.menu);
        // TODO: rendering the menu to the nearest overlfowing ancestor has been disabled
        // since DomQuery is no longer available by default in 5.0
        //        if (!menu.rendered) {
        //            menu.render(this.el.up('{overflow=auto}') || document.body);
        //        }
    },
 
    /**
     * Returns an array of menu items to be placed into the shared menu
     * across all headers in this header container.
     * @return {Array} menuItems
     */
    getMenuItems: function() {
        var me = this,
            menuItems = [],
            hideableColumns = me.enableColumnHide ? me.getColumnMenu(me) : null;
 
        if (me.sortable) {
            menuItems = [{
                itemId: 'ascItem',
                text: me.sortAscText,
                iconCls: me.menuSortAscCls,
                handler: me.onSortAscClick,
                scope: me
            }, {
                itemId: 'descItem',
                text: me.sortDescText,
                iconCls: me.menuSortDescCls,
                handler: me.onSortDescClick,
                scope: me
            }];
        }
 
        if (hideableColumns && hideableColumns.length) {
            if (me.sortable) {
                menuItems.push({
                    itemId: 'columnItemSeparator',
                    xtype: 'menuseparator'
                });
            }
 
            menuItems.push({
                itemId: 'columnItem',
                text: me.columnsText,
                iconCls: me.menuColsIcon,
                menu: hideableColumns,
                hideOnClick: false
            });
        }
 
        return menuItems;
    },
 
    // sort asc when clicking on item in menu
    onSortAscClick: function() {
        var menu = this.getMenu(),
            activeHeader = menu.activeHeader;
 
        activeHeader.sort('ASC');
    },
 
    // sort desc when clicking on item in menu
    onSortDescClick: function() {
        var menu = this.getMenu(),
            activeHeader = menu.activeHeader;
 
        activeHeader.sort('DESC');
    },
 
    /**
     * Returns an array of menu CheckItems corresponding to all immediate children
     * of the passed Container which have been configured as hideable.
     */
    getColumnMenu: function(headerContainer) {
        var menuItems = [],
            i = 0,
            item,
            items = headerContainer.query('>gridcolumn[hideable]'),
            itemsLn = items.length,
            menuItem;
 
        for (; i < itemsLn; i++) {
            item = items[i];
            menuItem = new Ext.menu.CheckItem({
                text: item.menuText || item.text,
                checked: !item.hidden,
                hideOnClick: false,
                headerId: item.id,
                menu: item.isGroupHeader ? this.getColumnMenu(item) : undefined,
                checkHandler: this.onColumnCheckChange,
                scope: this
            });
            menuItems.push(menuItem);
        }
 
        // Prevent creating a submenu if we have no items
        return menuItems.length ? menuItems : null;
    },
 
    onColumnCheckChange: function(checkItem, checked) {
        var header = Ext.getCmp(checkItem.headerId);
 
        if (header.rendered) {
            header[checked ? 'show' : 'hide']();
        }
        else {
            header.hidden = !checked;
        }
    },
 
    /**
     * Returns the number of <b>grid columns</b> descended from this HeaderContainer.
     * Group Columns are HeaderContainers. All grid columns are returned, including hidden ones.
     */
    getColumnCount: function() {
        return this.getGridColumns().length;
    },
 
    /**
     * Gets the full width of all columns that are visible for setting width of tables.
     */
    getTableWidth: function() {
        var fullWidth = 0,
            headers = this.getVisibleGridColumns(),
            headersLn = headers.length,
            i;
 
        for (= 0; i < headersLn; i++) {
            fullWidth += headers[i].getCellWidth() || 0;
        }
 
        return fullWidth;
    },
 
    /**
     * Returns an array of the **visible** columns in the grid. This goes down to the
     * lowest column header level, and does not return **grouped** headers which contain
     * sub headers.
     *
     * See also {@link Ext.grid.header.Container#getGridColumns}
     * @return {Ext.grid.column.Column[]} columns An array of visible columns.  Returns
     * an empty array if no visible columns are found.
     */
    getVisibleGridColumns: function() {
        var me = this,
            allColumns, rootHeader,
            result, len, i, column;
 
        if (me.gridVisibleColumns) {
            return me.gridVisibleColumns;
        }
 
        allColumns = me.getGridColumns();
        rootHeader = me.getRootHeaderCt();
        result = [];
        len = allColumns.length;
 
        // Use an inline check instead of ComponentQuery filtering for better performance for
        // repeated grid row rendering - as in buffered rendering.
        for (= 0; i < len; i++) {
            column = allColumns[i];
 
            if (!column.hidden && !column.isColumnHidden(rootHeader)) {
                result[result.length] = column;
            }
        }
 
        me.gridVisibleColumns = result;
 
        return result;
    },
 
    isColumnHidden: function(rootHeader) {
        var owner = this.getRefOwner();
 
        while (owner && owner !== rootHeader) {
            if (owner.hidden) {
                return true;
            }
 
            owner = owner.getRefOwner();
        }
 
        return false;
    },
 
    /**
     * @method getGridColumns
     * Returns an array of all columns which exist in the grid's View, visible or not.
     * This goes down to the leaf column header level, and does not return **grouped**
     * headers which contain sub headers.
     *
     * It includes hidden headers even though they are not rendered. This is for
     * collection of menu items for the column hide/show menu.
     *
     * Headers which have a hidden ancestor have a `hiddenAncestor: true` property
     * injected so that descendants are known to be hidden without interrogating that
     * header's ownerCt axis for a hidden ancestor.
     *
     * See also {@link Ext.grid.header.Container#getVisibleGridColumns}
     * @return {Ext.grid.column.Column[]} columns An array of columns.  Returns an
     * empty array if no columns are found.
     */
    getGridColumns: function(/* private - used in recursion */inResult, hiddenAncestor) {
        if (!inResult && this.gridDataColumns) {
            return this.gridDataColumns;
        }
 
        // eslint-disable-next-line vars-on-top
        var me = this,
            result = inResult || [],
            items, i, len, item,
            lastVisibleColumn;
 
        hiddenAncestor = hiddenAncestor || me.hidden;
 
        if (me.items) {
            items = me.items.items;
 
            // An ActionColumn (Columns extend HeaderContainer) may have an items *array*
            // being the action items that it renders.
            if (items) {
                for (= 0, len = items.length; i < len; i++) {
                    item = items[i];
 
                    if (item.isGroupHeader) {
                        // Group headers will need a visibleIndex for if/when they're removed
                        // from their owner.
                        // See Ext.layout.container.Container#moveItemBefore.
                        item.visibleIndex = result.length;
                        item.getGridColumns(result, hiddenAncestor);
                    }
                    else {
                        item.hiddenAncestor = hiddenAncestor;
                        result.push(item);
                    }
                }
            }
        }
 
        if (!inResult) {
            me.gridDataColumns = result;
        }
 
        // If top level, correct first and last visible column flags
        if (!inResult && len) {
            // Set firstVisible and lastVisible flags
            for (= 0, len = result.length; i < len; i++) {
                item = result[i];
 
                // The column index within all (visible AND hidden) leaf level columns.
                // Used as the cellIndex in TableView's cell renderer call
                item.fullColumnIndex = i;
                item.isFirstVisible = item.isLastVisible = false;
 
                if (!(item.hidden || item.hiddenAncestor)) {
                    if (!lastVisibleColumn) {
                        item.isFirstVisible = true;
                    }
 
                    lastVisibleColumn = item;
                }
            }
 
            // If we haven't hidden all columns, tag the last visible one encountered
            if (lastVisibleColumn) {
                lastVisibleColumn.isLastVisible = true;
            }
        }
 
        return result;
    },
 
    /**
     * @private
     * For use by column headers in determining whether there are any hideable columns
     * when deciding whether or not
     * the header menu should be disabled.
     */
    getHideableColumns: function() {
        var me = this,
            result = me.hideableColumns;
 
        if (!result) {
            result = me.hideableColumns = me.query('[hideable]');
        }
 
        return result;
    },
 
    /**
     * Returns the index of a leaf level header regardless of what the nesting
     * structure is.
     *
     * If a group header is passed, the index of the first leaf level header within it is returned.
     *
     * @param {Ext.grid.column.Column} header The header to find the index of
     * @return {Number} The index of the specified column header
     */
    getHeaderIndex: function(header) {
        // Binding the columnManager to a column makes it backwards-compatible with versions
        // that only bind the columnManager to a root header.
        if (!this.columnManager) {
            this.columnManager = this.getRootHeaderCt().columnManager;
        }
 
        return this.columnManager.getHeaderIndex(header);
    },
 
    /**
     * Get a leaf level header by index regardless of what the nesting
     * structure is.
     *
     * @param {Number} index The column index for which to retrieve the column.
     */
    getHeaderAtIndex: function(index) {
        // Binding the columnManager to a column makes it backwards-compatible with versions
        // that only bind the columnManager to a root header.
        if (!this.columnManager) {
            this.columnManager = this.getRootHeaderCt().columnManager;
        }
 
        return this.columnManager.getHeaderAtIndex(index);
    },
 
    /**
     * When passed a column index, returns the closet *visible* column to that. If the column
     * at the passed index is visible, that is returned. If it is hidden, either the next visible,
     * or the previous visible column is returned.
     *
     * @param {Number} index Position at which to find the closest visible column.
     */
    getVisibleHeaderClosestToIndex: function(index) {
        // Binding the columnManager to a column makes it backwards-compatible with versions
        // that only bind the columnManager to a root header.
        if (!this.visibleColumnManager) {
            this.visibleColumnManager = this.getRootHeaderCt().visibleColumnManager;
        }
 
        return this.visibleColumnManager.getVisibleHeaderClosestToIndex(index);
    },
 
    applyForceFit: function(header) {
        var me = this,
            view = me.view,
            minWidth = Ext.grid.plugin.HeaderResizer.prototype.minColWidth,
            // Used when a column's max contents are larger than the available view width.
            useMinWidthForFlex = false,
            defaultWidth = Ext.grid.header.Container.prototype.defaultWidth,
            // eslint-disable-next-line max-len
            availFlex = me.el.dom.clientWidth - (view.el.dom.scrollHeight > view.el.dom.clientHeight ? Ext.scrollbar.width() : 0),
            totalFlex = 0,
            items = me.getVisibleGridColumns(),
            hidden = header.hidden,
            len, i,
            item,
            maxAvailFlexOneColumn,
            myWidth;
 
        function getTotalFlex() {
            for (= 0, len = items.length; i < len; i++) {
                item = items[i];
 
                // Skip the current header.
                if (item === header) {
                    continue;
                }
 
                item.flex = item.flex || item.width || item.getWidth();
                totalFlex += item.flex;
                item.width = null;
            }
        }
 
        function applyWidth() {
            // The currently-sized column (whether resized or reshown) will already
            // have a width, so all other columns will need to be flexed.
            var isCurrentHeader;
 
            for (= 0, len = items.length; i < len; i++) {
                item = items[i];
                isCurrentHeader = (item === header);
 
                if (useMinWidthForFlex && !isCurrentHeader) {
                    // The selected column is extremely large so set all the others 
                    // as flex minWidth.
                    item.flex = minWidth;
                    item.width = null;
                }
                else if (!isCurrentHeader) {
                    // Note that any widths MUST be converted to flex. Imagine that all but one
                    // columns are hidden.  The widths of each column very easily could be greater
                    // than the total available width (think about the how visible header widths
                    // increase as sibling columns are hidden), so they cannot be reliably used
                    // to size the header, and the only safe approach is to convert any all widths
                    // to flex (except for the current header).
                    myWidth = item.flex || defaultWidth;
                    item.flex = Math.max(Math.ceil((myWidth / totalFlex) * availFlex), minWidth);
                    item.width = null;
                }
 
                item.setWidth(item.width || item.flex);
            }
        }
 
        Ext.suspendLayouts();
 
        // Determine the max amount of flex that a single column can have.
        maxAvailFlexOneColumn = (availFlex - ((items.length + 1) * minWidth));
 
        // First, remove the header's flex as it should always receive a set width
        // since it is the header being operated on.
        header.flex = null;
 
        if (hidden) {
            myWidth = header.width || header.savedWidth ||
                      Math.floor(maxAvailFlexOneColumn / (items.length + 1));
            header.savedWidth = null;
        }
        else {
            myWidth = view.getMaxContentWidth(header);
        }
 
        // We need to know if the max content width of the selected column would blow out the
        // grid. If so, all the other visible columns will be flexed to minWidth. 
        if (myWidth > maxAvailFlexOneColumn) {
            header.width = maxAvailFlexOneColumn;
            useMinWidthForFlex = true;
        }
        else {
            header.width = myWidth;
 
            // Substract the current header's width from the available flex + some padding
            // to ensure that the last column doesn't get nudged out of the view.
            availFlex -= myWidth + defaultWidth;
            getTotalFlex();
        }
 
        applyWidth();
 
        Ext.resumeLayouts(true);
    },
 
    autoSizeColumn: function(header) {
        var view = this.view;
 
        if (view) {
            view.autoSizeColumn(header);
 
            if (this.forceFit) {
                this.applyForceFit(header);
            }
        }
    },
 
    getRefItems: function(deep) {
    // Override to include the header menu in the component tree
        var result = this.callParent([deep]);
 
        if (this.menu) {
            result.push(this.menu);
        }
 
        return result;
    },
 
    initInheritedState: function(inheritedState, inheritedStateInner) {
        if (this.sealed) {
            inheritedState.sealed = true;
        }
 
        this.callParent([inheritedState, inheritedStateInner]);
    },
 
    privates: {
        beginChildHide: function() {
            ++this.childHideCount;
        },
 
        endChildHide: function() {
            --this.childHideCount;
        },
 
        getFocusables: function() {
            return this.isRootHeader
                ? this.getVisibleGridColumns()
                : this.items.items;
        },
 
        initFocusableContainerKeyNav: function(el) {
            var me = this;
 
            if (!me.focusableKeyNav) {
                me.focusableKeyNav = new Ext.util.KeyNav({
                    target: el,
                    scope: me,
 
                    down: me.showHeaderMenu,
                    left: me.onFocusableContainerLeftKey,
                    right: me.onFocusableContainerRightKey,
                    home: me.onHomeKey,
                    end: me.onEndKey,
 
                    space: me.onHeaderActivate,
                    enter: me.onHeaderActivate
                });
            }
        },
 
        onHomeKey: function(e) {
            return this.focusChild(null, true, e);
        },
 
        onEndKey: function(e) {
            return this.focusChild(null, false, e);
        },
 
        showHeaderMenu: function(e) {
            var column = this.getFocusableFromEvent(e);
 
            // DownArrow event must be from a column, not a Component within the column
            // (eg filter fields)
            if (column && column.isColumn && column.triggerEl) {
                this.onHeaderTriggerClick(column, e, column.triggerEl);
            }
        },
 
        onHeaderActivate: function(e) {
            var column = this.getFocusableFromEvent(e),
                view,
                lastFocused;
 
            // Remember that not every descendant of a headerCt is a column!
            // It could be a child component of a column.
            if (column && column.isColumn) {
                view = column.getView();
 
                // Sort the column is configured that way.
                // sortOnClick may be set to false by SpreadsheelSelectionModel
                // to allow click to select a column.
                if (column.sortable && this.sortOnClick) {
                    lastFocused = view.getNavigationModel().getLastFocused();
                    column.toggleSortState();
 
                    // After keyboard sort, bring last focused record into view
                    if (lastFocused) {
                        view.ownerCt.ensureVisible(lastFocused.record);
                    }
                }
                else if (e.getKey() === e.SPACE) {
                    column.onTitleElClick(e, e.target, this.sortOnClick);
                }
 
                // onHeaderClick is a necessary part of accessibility processing, sortable or not.
                return this.onHeaderClick(column, e, column.el);
            }
        },
 
        onOwnerGridReconfigure: function(storeChanged, columnsChanged) {
            var me = this;
 
            if (!me.rendered || me.destroying || me.destroyed) {
                return;
            }
 
            // Adding or removing columns during reconfiguration could result
            // in changed FocusableContainer state.
            if (storeChanged || columnsChanged) {
                if (Ext.Component.layoutSuspendCount) {
                    me.$initFocusableContainerAfterLayout = true;
                }
                else {
                    me.initFocusableContainer();
                }
            }
        }
    }
});