text: import {select} from 'd3-selection';
  
  import {events} from '../core/events';
  import {BaseMixin} from '../base/base-mixin';
  import {utils} from '../core/utils'
  import {d3compat} from '../core/config';
  
  const GROUP_CSS_CLASS = 'dc-cbox-group';
  const ITEM_CSS_CLASS = 'dc-cbox-item';
  
  /**
   * The CboxMenu is a simple widget designed to filter a dimension by
   * selecting option(s) from a set of HTML `<input />` elements. The menu can be
   * made into a set of radio buttons (single select) or checkboxes (multiple).
   * @mixes BaseMixin
   */
  export class CboxMenu extends BaseMixin {
      /**
       * Create a Cbox Menu.
       *
       * @example
       * // create a cboxMenu under #cbox-container using the default global chart group
       * var cbox = new CboxMenu('#cbox-container')
       *                .dimension(states)
       *                .group(stateGroup);
       * // the option text can be set via the title() function
       * // by default the option text is '`key`: `value`'
       * cbox.title(function (d){
       *     return 'STATE: ' + d.key;
       * })
       * @param {String|node|d3.selection|CompositeChart} parent - Any valid
       * [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying
       * a dom block element such as a div; or a dom element or d3 selection.
       * @param {String} [chartGroup] - The name of the chart group this widget should be placed in.
       * Interaction with the widget will only trigger events and redraws within its group.
       */
      constructor (parent, chartGroup) {
          super();
  
          this._cbox = undefined;
          this._promptText = 'Select all';
          this._multiple = false;
          this._inputType = 'radio';
          this._promptValue = null;
  
          this._uniqueId = utils.uniqueId();
  
          this.data(group => group.all().filter(this._filterDisplayed));
  
          // There is an accessor for this attribute, initialized with default value
          this._filterDisplayed = d => this.valueAccessor()(d) > 0;
  
          this._order = (a, b) => {
              if (this.keyAccessor()(a) > this.keyAccessor()(b)) {
                  return 1;
              }
              if (this.keyAccessor()(a) < this.keyAccessor()(b)) {
                  return -1;
              }
              return 0;
          };
  
          this.anchor(parent, chartGroup);
      }
  
      _doRender () {
          return this._doRedraw();
      }
  
      _doRedraw () {
          this.select('ul').remove();
          this._cbox = this.root()
              .append('ul')
              .classed(GROUP_CSS_CLASS, true);
          this._renderOptions();
  
          if (this.hasFilter() && this._multiple) {
              this._cbox.selectAll('input')
              // adding `false` avoids failing test cases in phantomjs
                  .property('checked', d => d && this.filters().indexOf(String(this.keyAccessor()(d))) >= 0 || false);
          } else if (this.hasFilter()) {
              this._cbox.selectAll('input')
                  .property('checked', d => {
                      if (!d) {
                          return false;
                      }
                      return this.keyAccessor()(d) === this.filter();
                  });
          }
          return this;
      }
  
      _renderOptions () {
          let options = this._cbox
              .selectAll(`li.${ITEM_CSS_CLASS}`)
              .data(this.data(), d => this.keyAccessor()(d));
  
          options.exit().remove();
  
          options = options.enter()
              .append('li')
              .classed(ITEM_CSS_CLASS, true)
              .merge(options);
  
          options
              .append('input')
              .attr('type', this._inputType)
              .attr('value', d => this.keyAccessor()(d))
              .attr('name', `domain_${this._uniqueId}`)
              .attr('id', (d, i) => `input_${this._uniqueId}_${i}`);
          options
              .append('label')
              .attr('for', (d, i) => `input_${this._uniqueId}_${i}`)
              .text(this.title());
  
          const chart = this;
          // 'all' option
          if (this._multiple) {
              this._cbox
                  .append('li')
                  .append('input')
                  .attr('type', 'reset')
                  .text(this._promptText)
                  .on('click', d3compat.eventHandler(function (d, evt) {
                      return chart._onChange(d, evt, this);
                  }));
          } else {
              const li = this._cbox.append('li');
              li.append('input')
                  .attr('type', this._inputType)
                  .attr('value', this._promptValue)
                  .attr('name', `domain_${this._uniqueId}`)
                  .attr('id', (d, i) => `input_${this._uniqueId}_all`)
                  .property('checked', true);
              li.append('label')
                  .attr('for', (d, i) => `input_${this._uniqueId}_all`)
                  .text(this._promptText);
          }
  
          this._cbox
              .selectAll(`li.${ITEM_CSS_CLASS}`)
              .sort(this._order);
  
          this._cbox.on('change', d3compat.eventHandler(function (d, evt) {
              return chart._onChange(d, evt, this);
          }));
          return options;
      }
  
      _onChange (d, evt, element) {
          let values;
  
          const target = select(evt.target);
          let options;
  
          if (!target.datum()) {
              values = this._promptValue || null;
          } else {
              options = select(element).selectAll('input')
                  .filter(function (o) {
                      if (o) {
                          return this.checked;
                      }
                  });
              values = options.nodes().map(option => option.value);
              // check if only prompt option is selected
              if (!this._multiple && values.length === 1) {
                  values = values[0];
              }
          }
          this.onChange(values);
      }
  
      onChange (val) {
          if (val && this._multiple) {
              this.replaceFilter([val]);
          } else if (val) {
              this.replaceFilter(val);
          } else {
              this.filterAll();
          }
          events.trigger(() => {
              this.redrawGroup();
          });
      }
  
      /**
       * Get or set the function that controls the ordering of option tags in the
       * cbox menu. By default options are ordered by the group key in ascending
       * order.
       * @param {Function} [order]
       * @returns {Function|CboxMenu}
       * @example
       * // order by the group's value
       * chart.order(function (a,b) {
       *     return a.value > b.value ? 1 : b.value > a.value ? -1 : 0;
       * });
       */
      order (order) {
          if (!arguments.length) {
              return this._order;
          }
          this._order = order;
          return this;
      }
  
      /**
       * Get or set the text displayed in the options used to prompt selection.
       * @param {String} [promptText='Select all']
       * @returns {String|CboxMenu}
       * @example
       * chart.promptText('All states');
       */
      promptText (promptText) {
          if (!arguments.length) {
              return this._promptText;
          }
          this._promptText = promptText;
          return this;
      }
  
      /**
       * Get or set the function that filters options prior to display. By default options
       * with a value of < 1 are not displayed.
       * @param {function} [filterDisplayed]
       * @returns {Function|CboxMenu}
       * @example
       * // display all options override the `filterDisplayed` function:
       * chart.filterDisplayed(function () {
       *     return true;
       * });
       */
      filterDisplayed (filterDisplayed) {
          if (!arguments.length) {
              return this._filterDisplayed;
          }
          this._filterDisplayed = filterDisplayed;
          return this;
      }
  
      /**
       * Controls the type of input element. Setting it to true converts
       * the HTML `input` tags from radio buttons to checkboxes.
       * @param {boolean} [multiple=false]
       * @returns {Boolean|CboxMenu}
       * @example
       * chart.multiple(true);
       */
      multiple (multiple) {
          if (!arguments.length) {
              return this._multiple;
          }
          this._multiple = multiple;
          if (this._multiple) {
              this._inputType = 'checkbox';
          } else {
              this._inputType = 'radio';
          }
          return this;
      }
  
      /**
       * Controls the default value to be used for
       * [dimension.filter](https://github.com/crossfilter/crossfilter/wiki/API-Reference#dimension_filter)
       * when only the prompt value is selected. If `null` (the default), no filtering will occur when
       * just the prompt is selected.
       * @param {?*} [promptValue=null]
       * @returns {*|CboxMenu}
       */
      promptValue (promptValue) {
          if (!arguments.length) {
              return this._promptValue;
          }
          this._promptValue = promptValue;
  
          return this;
      }
  }
  
  export const cboxMenu = (parent, chartGroup) => new CboxMenu(parent, chartGroup);
  