/**
 * This plugin enables user-defined filters for a grid.
 * @since 6.7.0
 *
 * In general an gridfilters plugin will be passed to the grid:
 *
 * ```javascript
 * @example({ framework: 'extjs' })
 *  var store = Ext.create('Ext.data.Store', {
 *      fields: ['firstname', 'lastname', 'seniority', 'department', 'hired', 'active'],
 *      data: [
 *          {
 *               firstname:"Michael",
 *               lastname:"Scott",
 *               seniority:7,
 *               department:"Management",
 *               hired:"01/10/2004",
 *               active: true
 *          },
 *          {
 *               firstname:"Dwight",
 *               lastname:"Schrute",
 *               seniority:2,
 *               department:"Sales",
 *               hired:"04/01/2004",
 *               active: true
 *          },
 *          {
 *               firstname:"Jim",
 *               lastname:"Halpert",
 *               seniority:3,
 *               department:"Sales",
 *               hired:"02/22/2006",
 *               active: false
 *          },
 *          {
 *               firstname:"Kevin",
 *               lastname:"Malone",
 *               seniority:4,
 *               department:"Accounting",
 *               hired:"06/10/2007",
 *               active: true
 *          },
 *          {
 *               firstname:"Angela",
 *               lastname:"Martin",
 *               seniority:5,
 *               department:"Accounting",
 *               hired:"10/21/2008",
 *               active: false
 *          }
 *      ]
 *  });
 *
 *  Ext.create({
 *      xtype: 'grid',
 *      title: 'Filter Grid Demo',
 *      itemConfig: {
 *          viewModel: true
 *      },
 *      plugins: {
 *           gridfilters: true
 *      },
 *      store: store,
 *      columns: [
 *          {text: 'First Name',  dataIndex:'firstname'},
 *          {text: 'Last Name',  dataIndex:'lastname'},
 *          {text: 'Hired Month',  dataIndex:'hired'},
 *          {
 *              text: 'Department',
 *              width: 200,
 *              cell: {
 *                 bind: '{record.department} ({record.seniority})'
 *              }
 *          }
 *      ],
 *      width: 500,
 *      fullscreen: true
 *  });
 * ```
 * ```html
 * @example({framework: 'ext-web-components', packages:['ext-web-components'], tab: 1 })
 *  <ext-grid
 *    fullscreen="true"
 *    itemConfig='{ "viewmodel": true }'
 *    plugins='{"gridfilters": true}'
 *    title='Filter Grid Demo'
 *    width="500"
 *    onready="plugin.onGridReady"
 *  >
 *    <ext-column dataIndex='firstname' text='First Name'></ext-column>
 *    <ext-column dataIndex='lastname' text='Last Name'></ext-column>
 *    <ext-column dataIndex='hired' text='Hired Month'></ext-column>
 *    <ext-column
 *        bind='{record.department} ({record.seniority})'
 *        text='Department'
 *        width="200"
 *    >
 *    </ext-column>
 *  </ext-grid>
 * ```
 * ```javascript
 * @example({framework: 'ext-web-components', tab: 2, packages: ['ext-web-components']})
 * import '@sencha/ext-web-components/dist/ext-grid.component';
 * import '@sencha/ext-web-components/dist/ext-column.component';
 *
 * export default class PluginComponent {
 *     constructor() {
 *         this.store=Ext.create('Ext.data.Store', {
 *             fields: ['firstname', 'lastname', 'seniority', 'department', 'hired', 'active'],
 *                 data: [{
 *                     firstname:"Michael",
 *                     lastname:"Scott",
 *                     seniority:7,
 *                     department:"Management",
 *                     hired:"01/10/2004",
 *                     active: true
 *                },
 *                {
 *                     firstname:"Dwight",
 *                     lastname:"Schrute",
 *                     seniority:2,
 *                     department:"Sales",
 *                     hired:"04/01/2004",
 *                     active: true
 *                },
 *                {
 *                     firstname:"Jim",
 *                     lastname:"Halpert",
 *                     seniority:3,
 *                     department:"Sales",
 *                     hired:"02/22/2006",
 *                     active: false
 *                },
 *                {
 *                     firstname:"Kevin",
 *                     lastname:"Malone",
 *                     seniority:4,
 *                     department:"Accounting",
 *                     hired:"06/10/2007",
 *                     active: true
 *                },
 *                {
 *                     firstname:"Angela",
 *                     lastname:"Martin",
 *                     seniority:5,
 *                     department:"Accounting",
 *                     hired:"10/21/2008",
 *                     active: false
 *                }
 *            ]
 *        });
 *     }
 * 
 *     onGridReady(event) {
 *         this.gridCmp = event.detail.cmp;
 *         this.gridCmp.setStore(this.store);
 *     }
 * }
 *
 * window.plugin = new PluginComponent();
 * ```
 * ```javascript
 * @example({framework: 'ext-react', packages:['ext-react']})
 * var store = Ext.create('Ext.data.Store', {
 *     fields: ['firstname', 'lastname', 'seniority', 'department', 'hired', 'active'],
 *     data: [
 *         {
 *              firstname:"Michael",
 *              lastname:"Scott",
 *              seniority:7,
 *              department:"Management",
 *              hired:"01/10/2004",
 *              active: true
 *         },
 *         {
 *              firstname:"Dwight",
 *              lastname:"Schrute",
 *              seniority:2,
 *              department:"Sales",
 *              hired:"04/01/2004",
 *              active: true
 *         },
 *         {
 *              firstname:"Jim",
 *              lastname:"Halpert",
 *              seniority:3,
 *              department:"Sales",
 *              hired:"02/22/2006",
 *              active: false
 *         },
 *         {
 *              firstname:"Kevin",
 *              lastname:"Malone",
 *              seniority:4,
 *              department:"Accounting",
 *              hired:"06/10/2007",
 *              active: true
 *         },
 *         {
 *              firstname:"Angela",
 *              lastname:"Martin",
 *              seniority:5,
 *              department:"Accounting",
 *              hired:"10/21/2008",
 *              active: false
 *         }
 *     ]
 * });
 *
 * render() {
 *     return (
 *       <ExtGrid
 *           fullscreen
 *           itemConfig={{
 *               viewModel: true
 *           }}
 *           plugins={{
 *               gridfilters: true
 *           }}
 *           store={store} 
 *           title='Filter Grid Demo'
 *           width={500}
 *       >
 *         <ExtColumn 
 *             dataIndex='firstname'
 *             text='First Name'
 *         />
 *         <ExtColumn 
 *             dataIndex='lastname'
 *             text='Last Name'
 *         />
 *         <ExtColumn 
 *             dataIndex='hired'
 *             text='Hired Month'
 *         />
 *         <ExtColumn 
 *             bind='{record.department} ({record.seniority})'
 *             text='Department'
 *             width={200}
 *         />
 *       </ExtGrid>
 *     );
 * }
 * ```
 * ```javascript
 * @example({framework: 'ext-angular', packages:['ext-angular']})
 * import { Component } from '@angular/core'
 * declare var Ext: any;
 *
 * Ext.require('Ext.grid.filters');
 * @Component({
 *     selector: 'app-root-1',
 *     styles: [`
 *             `],
 *     template: `
 *       <ExtGrid
 *           [height]="600"
 *           [title]="'Filter Grid Demo'"
 *           [itemConfig]="{viewModel: true}"
 *           [plugins]="{gridfilters: true}"
 *           [store]="store"
 *           [columns]="columns"
 *       ></ExtGrid>
 *     `
 * })
 * export class AppComponent {
 *   store = Ext.create('Ext.data.Store', {
 *       fields: ['firstname', 'lastname', 'seniority', 'department', 'hired', 'active'],
 *       data: [
 *           {
 *               firstname:"Michael",
 *               middlename:"Phineas",
 *               lastname:"Scott",
 *               seniority:7,
 *               department:"Management",
 *               hired:"01/10/2004",
 *               active: true
 *           },
 *           {
 *               firstname:"Dwight",
 *               middlename:"Thaddeus",
 *               lastname:"Schrute",
 *               seniority:2,
 *               department:"Sales",
 *               hired:"04/01/2004",
 *               active: true
 *           },
 *           {
 *               firstname:"Jim",
 *               middlename:"Ezekiel",
 *               lastname:"Halpert",
 *               seniority:3,
 *               department:"Sales",
 *               hired:"02/22/2006",
 *               active: false
 *           },
 *           {
 *               firstname:"Kevin",
 *               middlename:"Jethro",
 *               lastname:"Malone",
 *               seniority:4,
 *               department:"Accounting",
 *               hired:"06/10/2007",
 *               active: true
 *           },
 *           {
 *               firstname:"Angela",
 *               middlename:"Rebecca",
 *               lastname:"Martin",
 *               seniority:5,
 *               department:"Accounting",
 *               hired:"10/21/2008",
 *               active: false
 *           }
 *       ]
 *   });
 *
 *   columns = [
 *       {text: 'First Name', width: 120, dataIndex:'firstname'},
 *       {text: 'Middle Name',  width: 120, dataIndex:'middlename'},
 *       {text: 'Last Name',  width: 120, dataIndex:'lastname'},
 *       {text: 'Hired Month', width: 150, dataIndex:'hired'},
 *       {text: 'Department', width: 200, cell: {bind: '{record.department} ({record.seniority})'}}
 *   ]
 * }
 * ```
 *
 * # Convenience Subclasses
 *
 * There are several menu subclasses that provide default rendering for various data types
 *
 *  - {@link Ext.grid.filters.menu.Boolean}: Renders for boolean input fields
 *  - {@link Ext.grid.filters.menu.Date}: Renders for date input fields
 *  - {@link Ext.grid.filters.menu.Number}: Renders for numeric input fields
 *  - {@link Ext.grid.filters.menu.String}: Renders for string input fields
 *  - {@link Ext.grid.filters.menu.List}: Used for a range of values, but must be explicitly 
 *  configured
 *
 *  These subclasses can be configured in columns as such:
 *
 *
 *      columns: [
 *          {text: 'First Name',  dataIndex:'firstname'},
 *          {text: 'Middle Name',  dataIndex:'firstname', 'filter': 'list'},
 *          {text: 'Last Name',  dataIndex:'lastname', filter: 'string'},
 *          {text: 'seniority', dataIndex: 'seniority', filter: 'number'},
 *          {text: 'Hired Month',  dataIndex:'hired', filter: 'date'},
 *          {text: 'Active',  dataIndex:'active', filter: 'boolean'}
 *      ]
 *
 *
 *  Menu items can also be customised as shown below. 
 *  
 *  Note that most filters specify menu.items members as filter operators. The list filter is 
 *  different: it's backed by a {@link Ext.field.ComboBox combobox} on the menu.items.list property.
 *  The combobox {@link Ext.field.ComboBox#multiSelect multiSelect} config is used to determine
 *  the operator dynamically and defaults to single select which uses == for the selected
 *  value, whereas multiSelect is `true`, uses "in" with the array of selected values.
 *
 *      columns: [
 *          {
 *              text: 'First Name',
 *              dataIndex:'firstname'
 *          },
 *          {
 *              text: 'Middle Name',
 *              filter: {
 *                  type: 'list',
 *                  menu: {
 *                      items: {
 *                          list: {
 *                              placeholder: 'Choose something',
 *                              multiSelect: true
 *                          }
 *                      }
 *                  }
 *              }
 *          },
 *          {
 *              text: 'Last Name',
 *              filter: {
 *                  type: 'string',
 *                  menu: {
 *                      items: {
 *                          like: {
 *                              placeholder: 'Custom Like...'
 *                          }
 *                      }
 *                  }
 *              }
 *          },
 *          {
 *              text: 'Hired Month',
 *              filter: {
 *                  type: 'date',
 *                  menu: {
 *                      items: {
 *                          lt: {
 *                              label: 'Custom Less than',
 *                              placeholder: 'Custom Less than...',
 *                              dateFormat: 'd-m-y'
 *                          },
 *                          gt: {
 *                              label: 'Custom Greater than',
 *                              placeholder: 'Custom Greater than...',
 *                              dateFormat: 'd-m-y'
 *                          },
 *                          eq: {
 *                              label: 'Custom On',
 *                              placeholder: 'Custom On...',
 *                              dateFormat: 'd-m-y'
 *                          }
 *                      }
 *                  }
 *              }
 *          },
 *          {
 *              text: 'seniority'
 *              filter: {
 *                  type: 'number',
 *                  menu: {
 *                      items: {
 *                          lt: {
 *                              label: 'Custom Less than',
 *                              placeholder: 'Custom Less than...',
 *                          },
 *                          gt: {
 *                              label: 'Custom Greater than',
 *                              placeholder: 'Custom Greater than...',
 *                          },
 *                          eq: {
 *                              label: 'Custom Equal to',
 *                              placeholder: 'Custom Equal to...',
 *                          }
 *                      }
 *                  }
 *              }
 *          },
 *          {
 *              text: 'Active',
 *              filter: {
 *                  type: 'boolean',
 *                  menu: {
 *                      items: {
 *                          yes: {
 *                              text: 'Custom True'
 *                          },
 *                          no: {
 *                              text: 'Custom False'
 *                          }
 *                      }
 *                  }
 *              }             
 *          }
 *      ]
 *
 *  Filters can also specify "value", which is the initial value for the filter. To use this you
 *  can either specify a value appropriate for the filter type, or for filters that use <, =, 
 *  and > operators, the initial value for the operator. Here are some examples:
 * 
 *      {
 *          text: 'Active',
 *          filter: {
 *              type: 'boolean',
 *              value: true
 *          }
 *      }
 *
 *      {
 *          text: 'Seniority'
 *          filter: {
 *              type: 'number',
 *              value: 7
 *          }
 *      }
 *
 *      {
 *          text: 'Seniority'
 *          filter: {
 *              type: 'number',
 *              value: {
 *                  "<": 100,
 *                  ">": 10
 *              }
 *          }
 *      }
 *
 *      {
 *          text: 'Hired Month',
 *          filter: {
 *              type: 'date',
 *              value: {
 *                  "<": new Date()
 *              }
 *          }
 *      }
 * 
 *      {
 *          text: 'Last Name',
 *          filter: {
 *              type: 'list',
 *              value: 'Smith'
 *          }
 *      }
 *      
 *      {
 *          text: 'Last Name',
 *          filter: {
 *              type: 'list',
 *              value: ['Smith', 'Jones], // This is an [] because the combobox is multiSelect:true
 *              menu: {
 *                  list: {
 *                      multiSelect: true
 *                  }
 *              }
 *          }
 *      }
 * 
 */
Ext.define('Ext.grid.filters.Plugin', {
    extend: 'Ext.plugin.Abstract',
    alias: 'plugin.gridfilters',
 
    requires: [
        'Ext.grid.filters.menu.*',
        'Ext.grid.filters.Column', // an override that extends Column w/filter config
        'Ext.data.Query',
        'Ext.util.Filter'
    ],
 
    mixins: [
        'Ext.state.Stateful',
        'Ext.mixin.StoreWatcher',
        'Ext.mixin.Bufferable'
    ],
 
    // The query modified is buffered by avoid the duplicate
    // loads in the initial start up process.
    bufferableMethods: {
        queryModified: 100
    },
 
    config: {
        /**
         * @cfg {String/Object} activeFilter
         * This config holds the current filter. This config is stateful.
         * @private
         */
        activeFilter: null,
 
        query: {
            type: 'default',
            format: 'filters'
        }
    },
 
    /**
     * @property {String} [filterCls="x-grid-filters-filtered-column"]
     * The CSS applied to column headers with active filters.
     */
    filterCls: Ext.baseCSSPrefix + 'grid-filters-filtered-column',
 
    stateful: [
        'activeFilter'
    ],
 
    init: function(grid) {
        this.setOwner(grid);
 
        grid.on({
            beforeshowcolumnmenu: 'onBeforeShowColumnMenu',
            scope: this
        });
 
        this.initColumns();
    },
 
    destroy: function() {
        this.setOwner(null);
 
        this.callParent();
    },
 
    // activeFilter
 
    updateActiveFilter: function(filter) {
        var query = this.getQuery();
 
        if (Ext.isString(filter)) {
            query.setSource(filter);
        }
        else {
            query.setFilters(filter);
        }
    },
 
    // query
 
    applyQuery: function(config, query) {
        return Ext.Factory.query.update(query, config);
    },
 
    updateQuery: function(query) {
        if (query) {
            // eslint-disable-next-line vars-on-top
            var me = this,
                fn = query.compile;
 
            query.compile = function() {
                fn.call(query);
                me.queryModified(query);
            };
        }
    },
 
    // store (StoreWatcher)
 
    updateStore: function(store, oldStore) {
        var me = this,
            query = me.getQuery();
 
        me.mixins.storewatcher.updateStore.call(me, store, oldStore);
 
        if (oldStore && !(oldStore.destroying || oldStore.destroyed)) {
            oldStore.getFilters().remove(query);
        }
 
        if (store) {
            store.getFilters().add(query);
        }
    },
 
    //--------------------------------------------
 
    onSetFilter: function(menuItem) {
        this.setActiveFilter(menuItem.rec.data.query);
    },
 
    privates: {
 
        /**
         * Create all filters.
         */
        initColumns: function() {
            var grid = this.getOwner(),
                columns = grid.getColumns(),
                len = columns.length,
                i, column;
 
            for (= 0; i < len; i++) {
                column = columns[i];
                this.createFilter(grid, column, column.getMenu());
            }
        },
 
        /**
         * Creates the Filter objects for the current configuration.
         * Reconfigure and on add handlers.
         */
        createFilter: function(grid, column, menu) {
            var me = this,
                filterMenuItem, menuConfig, filter;
 
            if (!menu) {
                return;
            }
 
            filterMenuItem = menu.getComponent('filter');
 
            if (!filterMenuItem) {
                // This method is provided by our Column override:
                menuConfig = column.createFilter({
                    itemId: 'filter',
                    plugin: me,
                    column: column
                });
 
                filterMenuItem = menuConfig && menu.add(Ext.Factory.gridFilters(menuConfig));
 
                if (filterMenuItem) {
                    filterMenuItem.setCheckHandler(me.onFilterItemCheckChange.bind(me));
 
                    filter = column.getFilter();
 
                    if (!Ext.isObject(filter) || filter.value === undefined) {
                        filterMenuItem.syncFilter();
                    }
                }
            }
 
            return filterMenuItem;
        },
 
        onBeforeShowColumnMenu: function(grid, column, menu) {
            var me = this,
                filterMenuItem = menu.getComponent('filter');
 
            // The column may have been added after the plugin
            // initialization.
            if (!filterMenuItem) {
                filterMenuItem = me.createFilter(grid, column, menu);
            }
 
            if (filterMenuItem) {
                filterMenuItem.syncFilter();
            }
        },
 
        onFilterItemCheckChange: function(item) {
            item.syncQuery();
        },
 
        doQueryModified: function() {
            var filters = this.cmp.getStore();
 
            filters = filters && filters.getFilters();
 
            if (filters) {
                filters.beginUpdate();
                ++filters.generation;
                filters.endUpdate();
            }
        }
    }
});