/**
 * A specialized `Ext.dataview.DataView` to display items based upon the structure of
 * the `Ext.Chip` component with the configurations of the "chips" drawn from named
 * fields in the DataView's records.
 *
 * This handles selection and deletion of chips.
 *     @example
 *     Ext.define('Contact', {
 *         extend: 'Ext.data.Model',
 *         fields: [{
 *             name: 'emailAddress'
 *         }, {
 *             name: 'picture'
 *         }, {
 *             name: 'role',
 *             type: 'int'
 *         }, {
 *             name: 'closable',
 *             calculate: function(data) {
 *                 return data.role > 2 ? false: true;
 *             }
 *         }, {
 *             name: 'iconCls',
 *             calculate: function(data) {
 *                 return data.role > 2 ? 'x-manager-icon' : 'x-employee-icon';
 *             }
 *         }]
 *     });
 *
 *     var store = new Ext.data.Store({
 *        model: 'Contact',
 *         data: [{
 *             emailAddress: '[email protected]',
 *             picture: 'https://www.sencha.com/assets/images/sencha-avatar-64x64.png',
 *             role: 1
 *         }, {
 *             emailAddress: '[email protected]',
 *             picture: 'https://www.sencha.com/assets/images/sencha-avatar-64x64.png',
 *             role: 2
 *         }, {
 *             emailAddress: '[email protected]',
 *             picture: 'https://www.sencha.com/assets/images/sencha-avatar-64x64.png',
 *             role: 3
 *         }]
 *     });
 *
 *     Ext.create({
 *         xtype: 'panel',
 *         title: 'Chip DataView',
 *         width: 400,
 *         height: 200,
 *         renderTo: document.body,
 *         border: true,
 *         bodyPadding: 5,
 *         items: {
 *             xtype: 'chipview',
 *             store: store,
 *             displayField: 'emailAddress',
 *             iconField: 'picture',
 *             closeHandler: function(chipview, location) {
 *                 store.remove(location.record);
 *
 *                 // Stop the event, otherwise the NavigationModel
 *                 // will try to click on the nonexistent Chip
 *                 return false;
 *             }
 *         }
 *     });
 *
 * @since 6.7.0
 */
Ext.define('Ext.dataview.ChipView', {
    extend: 'Ext.dataview.DataView',
    xtype: 'chipview',
 
    requires: [
        // We need this because we rely on Ext.Chip for styling.
        'Ext.Chip'
    ],
 
    classCls: Ext.baseCSSPrefix + 'chipview',
 
    hasIconCls: Ext.baseCSSPrefix + 'has-icon',
    closableCls: Ext.baseCSSPrefix + 'closable',
    iconElCls: Ext.baseCSSPrefix + 'icon-el ' + Ext.baseCSSPrefix + 'font-icon',
 
    /**
     * @cfg itemTpl
     * @readonly
     * The `itemTpl` for a ChipView is generated and should not be configured or set.
     */
    itemTpl: '<div class="' + Ext.baseCSSPrefix + 'body-el">' +
        '<div class="{_chipIconCls}" style="{_chipIconStyle}"></div>' +
        '<div class="' + Ext.baseCSSPrefix + 'text-el">' +
            '{_chipText}' +
        '</div>' +
        '<div class="' + Ext.baseCSSPrefix + 'close-el ' + Ext.baseCSSPrefix + 'font-icon"></div>' +
    '</div>',
 
    config: {
        /**
         * @cfg {String} iconField
         * The name of the property from the itemTpl's values to use
         * as the chip's {@link #cfg!icon}. Mutually exclusive with the {@link #cfg!icon}
         * config.
         */
        iconField: null,
 
        /**
         * @cfg {String} iconClsField
         * The name of the property from the itemTpl's values to use as the chip's
         * {@link #cfg!iconCls}. Mutually exclusive with the {@link #cfg!iconCls} config.
         */
        iconClsField: null,
 
        /**
         * @cfg {String} displayField
         * The name of the property from the itemTpl's values to display
         * as the chip's text. Mutually exclusive with the {@link #cfg!text} config.
         */
        displayField: 'text',
 
        /**
         * @cfg {String} closableField
         * The name of the property from the itemTpl's values whose truthiness value
         * determines closability. Mutually exclusive with the {@link #cfg!closable} config.
         */
        closableField: null,
 
        /**
         * @cfg {Boolean} closable
         * Configure as `false` to show the chips without close icons.
         * Only used if {@link #cfg!closableField} is not set.
         */
        closable: true,
 
        /**
         * @cfg {Function/String} closeHandler
         * @param {Ext.dom.Element} closeHandler.chipView This ChipView.
         * @param {Ext.dataview.Location} closeHandler.location The dataview location which
         * encapsulates the clicked chip.
         * The handler function to run when the close tool is tapped.
         */
        closeHandler: null,
 
        /**
         * @cfg {Object} scope
         * The scope (`this` reference) in which the configured {@link #closeHandler} will be
         * executed, unless the scope is a ViewController method name.
         */
        scope: null,
 
        /**
         * @cfg {String/String[]} ui
         * The ui or uis to be used on Chip elements.
         *
         * When an itemUi is configured, CSS class names are added to each chip item element,
         * created by appending the itemUi name(s) to each {@link #classCls} and/or
         * {@link #baseCls}.
         */
        itemUi: null,
 
        /**
         * @cfg {String/String[]/Ext.XTemplate} displayTpl
         * A template to be used to create the textual part of the chip body given its record
         * data.
         *
         * This config is mutually exclusive with the {@link #cfg!displayField} config.
         */
        displayTpl: null
    },
 
    listeners: {
        childtap: 'onChipTap',
        scope: 'this',
        priority: 3000
    },
 
    getItemClass: function(values) {
        // This override of the built in getter takes advantage of the fact that
        // Ext.dataview.DataView#getItemElementConfig passes the template's
        // values.
        var me = this,
            result = [me.callParent([values])],
            chipCls = Ext.Chip.prototype.getClassCls(),
            chipClsLen = chipCls.length,
            uis = me.getItemUi(),
            uiLen = uis && uis.length,
            i, j, c;
 
        // Add the Ext.Chip class's classCls names, and the ui extended classCls names.
        for (= 0; i < chipClsLen; i++) {
            c = chipCls[i];
            result.push(c);
 
            for (= 0; j < uiLen; j++) {
                result.push(+ '-' + uis[j]);
            }
        }
 
        if (me.hasIcon(values)) {
            result.push(me.hasIconCls);
        }
 
        if (me.getChipClosable(values)) {
            result.push(me.closableCls);
        }
 
        return result.join(' ');
    },
 
    onChipTap: function(chip, location) {
        var me = this,
            handler = me.getCloseHandler();
 
        if (handler && location.event.getTarget('.' + Ext.baseCSSPrefix + 'close-el')) {
            return Ext.callback(handler, me.getScope(), [me, location], 0, me);
        }
    },
 
    prepareData: function(data, index, record) {
        var me = this,
            tpl = me.getDisplayTpl();
 
        data._chipIconCls = me.getChipIconCls(data);
        data._chipIconStyle = me.getChipIconStyle(data);
        data._chipText = tpl ? tpl.apply(data) : data[me.getDisplayField()];
 
        return data;
    },
 
    privates: {
        applyItemUi: function(value) {
            // Ensure that we have an array
            return (typeof value === 'string') ? value.split(' ') : Ext.Array.from(value);
        },
 
        updateItemUi: function() {
            this.doRefresh();
        },
 
        applyDisplayTpl: function(config) {
            return Ext.XTemplate.get(config);
        },
 
        updateDisplayTpl: function() {
            if (!this.isConfiguring) {
                this.refresh();
            }
        },
 
        getChipIconUrl: function(values) {
            return Ext.resolveResource(values[this.getIconField()]);
        },
 
        getChipIconStyle: function(values) {
            var iconUrl = this.getChipIconUrl(values);
 
            return iconUrl ? 'background-image: url(' + iconUrl + ')' : '';
        },
 
        hasIcon: function(values) {
            var iconClsField = this.getIconClsField();
 
            return this.getChipIconUrl(values)
                ? true
                : (iconClsField ? !!values[iconClsField] : false);
        },
 
        getChipIconCls: function(values) {
            var iconClsField = this.getIconClsField(),
                iconCls = iconClsField ? values[iconClsField] : '';
 
            return this.iconElCls + ' ' + iconCls;
        },
 
        getChipClosable: function(values) {
            var closableField = this.getClosableField();
 
            return closableField ? !!values[closableField] : this.getClosable();
        },
 
        _onChildEvent: function(fn, e) {
            // Events in the ownerField's inputElement which may be inserted into the
            // ChipView should NOT trigger child events
            if (!this.ownerField || e.target !== this.ownerField.inputElement.dom) {
                return this.callParent([fn, e]);
            }
 
            return {};
        },
 
        syncEmptyState: function() {
            var field = this.ownerField;
 
            // If we're acting for an input field, sync the field's empty state with ours
            if (field) {
                field.syncEmptyState();
                field.syncLabelPlaceholder(false);
            }
        },
 
        syncItemRecord: function(options) {
            var me = this,
                record = options.record,
                recordIndex = options ? options.recordIndex : me.store.indexOf(record),
                data = me.gatherData(record, recordIndex);
 
            options.item.className = me.getItemClass(data);
 
            me.callParent([options]);
        }
    }
});