/**
 * This class manages a group in a `list` or `grid`. These objects arrive as parameters to
 * events and can be retrieved via the {@link Ext.dataview.List#method!groupFrom groupFrom}
 * method.
 * @since 7.0
 */
Ext.define('Ext.dataview.ListGroup', {
    requires: [
        'Ext.data.Group'
    ],
 
    $configPrefixed: false,
 
    config: {
        /**
         * @cfg {Boolean} collapsed
         * This config controls whether a group is expanded or collapsed. Setting this
         * config is equivalent to calling the `collapse`, `expand` or `toggleCollapsed`
         * methods. Setting this config will control the collapse state without firing
         * the {@link Ext.dataview.List#event!beforegroupcollapse beforegroupcollapse} or
         * {@link Ext.dataview.List#event!beforegroupexpand beforegroupexpand} event.
         * Call `toggleCollapsed` to allow these events to veto the state change.
         *
         * The initial collapse state of a group is determined at the `list` level from
         * its {@link Ext.dataview.List#cfg!collapsible collapsible} config:
         *
         *      {
         *          xtype: 'list',
         *          collapsible: {
         *              collapsed: true  // initial collapse state for groups
         *          }
         *      }
         */
        collapsed: null,
 
        /**
         * @cfg {Boolean} collapsible
         * Set to `false` to prevent a group from collapsing. Since these objects come
         * and go based on user driven grouping choices, it is often easier to listen to
         * the {@link Ext.dataview.List#event!beforegroupcollapse beforegroupcollapse}
         * and/or {@link Ext.dataview.List#event!beforegroupexpand beforegroupexpand}
         * events.
         */
        collapsible: null,
 
        /**
         * @cfg {Ext.Component} header
         * The group's header component. Typically a {@link Ext.dataview.ItemHeader} or
         * {@link Ext.grid.RowHeader}.
         * @readonly
         */
        header: null
    },
 
    /**
     * @property {Ext.data.Group} data
     * The underlying data for this group.
     * @readonly
     */
    data: null,
 
    /**
     * @property {Ext.dataview.List} list
     * The owning `list` (or `grid`) component.
     * @readonly
     */
    list: null,
 
    beginIndex: -1,
    endIndex: -1,
    nextGroup: null,
    previousGroup: null,
 
    constructor: function(config) {
        this.initConfig(config);
    },
 
    /**
     * Collapses this group by calling `toggleCollapsed(false)`. This can be vetoed by the
     * {@link Ext.dataview.List#event!beforegroupcollapse beforegroupcollapse} event.
     *
     * See also `expand` and `toggleCollapsed`.
     */
    collapse: function() {
        this.toggleCollapsed(true);
    },
 
    /**
     * Expands this group by calling `toggleCollapsed(true)`. This can be vetoed by the
     * {@link Ext.dataview.List#event!beforegroupexpand beforegroupexpand} event.
     *
     * See also `collapse` and `toggleCollapsed`.
     */
    expand: function() {
        this.toggleCollapsed(false);
    },
 
    getCollapseTool: function() {
        var header = this.peekHeader();
 
        return header && header.lookupTool('groupCollapser');
    },
 
    isAttached: function() {
        var me = this,
            data = me.data,
            list = me.list,
            group = list.store.getGroups(),
            expected;
 
        group = group.get(data.getGroupKey());
 
        expected = Ext.getExpando(group, list.expandoKey);
 
        // Since these objects are preserved on the data group, it is possible that
        // an instance exists that is orphaned.
        return expected === me;
    },
 
    /**
     * Toggles the collapse state this group. Before changing the collapse state, this
     * method fires a {@link Ext.dataview.List#event!beforegroupexpand beforegroupexpand}
     * or {@link Ext.dataview.List#event!beforegroupcollapse beforegroupcollapse} event.
     * This is unlike calling `setCollapsed` which will always change the collapse state
     * as directed.
     *
     * See also `collapse` and `expand`.
     * @param {Boolean} [collapsed] Pass `true` or `false` to specify the desired state
     * or `null`/`undefined` to toggle the current state.
     */
    toggleCollapsed: function(collapsed) {
        var me = this,
            list = me.list,
            event;
 
        if (me.getCollapsible() !== false && me.isAttached()) {
            if (collapsed == null) {
                collapsed = !me.getCollapsed();
            }
 
            event = 'beforegroup' + (collapsed ? 'collapse' : 'expand');
 
            if (list.fireEvent(event, list, me) !== false) {
                me.setCollapsed(collapsed);
            }
        }
    },
 
    //---------------------------------
    // Config handling
 
    applyCollapsed: function(collapsed) {
        return !!collapsed;
    },
 
    updateCollapsed: function(collapsed, oldCollapsed) {
        var me = this,
            list = me.list,
            collapser = list.getCollapsible(),
            tool = me.getCollapseTool(),
            header = me.peekHeader();
 
        if (me.isAttached()) {
            if (tool) {
                tool.setType(collapsed ? 'expand' : 'collapse');
                tool.setTooltip(collapsed
                    ? collapser.getExpandToolText()
                    : collapser.getCollapseToolText());
            }
 
            if (header) {
                header.el.toggleCls(collapser.collapsedCls, collapsed);
            }
 
            if (collapsed !== !!oldCollapsed) {
                list.syncGroupCollapse(me, collapsed);
            }
        }
    },
 
    updateHeader: function(header) {
        if (header) {
            // eslint-disable-next-line vars-on-top
            var me = this,
                collapsible = me.list.getCollapsible(),
                collapsed = me.getCollapsed();
 
            if (collapsible) {
                if (!me.getCollapseTool()) {
                    header.addTool(collapsible.getTool());
                }
 
                if (collapsed === !!collapsed) {
                    me.updateCollapsed(collapsed, collapsed);
                }
                else {
                    me.setCollapsed(!!collapsed);
                }
            }
        }
    },
 
    peekHeader: function() {
        var me = this,
            header = me.getHeader();
 
        if (header && (header.destroying || !me.isAttached())) {
            me.setHeader(header = null);
        }
 
        return header;
    }
 
}, function(ListGroup) {
    var target = ListGroup.prototype;
 
    // Within reason we want to mimic the Ext.data.Group interface since instances of
    // this class are being passed in places where an Ext.data.Group instance was being
    // passed (prior to 7.0).
    Ext.each([
        // Collection
        'contains', 'containsAll', 'containsKey', 'each', 'eachKey', 'find', 'findBy',
        'findIndex', 'findIndexBy', 'first', 'last', 'get', 'getAt', 'getByKey', 'getCount',
        'getRange', 'getValues', 'indexOf', 'indexOfKey',
        // Ext.data.Group
        'getSummaryRecord'
    ], function(name) {
        // We need Ext.each() to produce a new function closure per iteration...
        target[name] = function() {
            var data = this.data;
 
            return data && data[name].apply(data, arguments);
        };
    });
});