/* global $ */
var ko = require('knockout');
var kf = require('./koForm');
var dom = require('./dom');
var kd = require('./koDom');

/**
 * Creates a multi-select implemented with a draggable list of selected items followed by
 * an autocomplete input containing the remaining selectable items.
 *
 * Items in `selected` list can be arbitrary objects, and get passed to remove()/reorder().
 * Items for auto-complete should have 'value' and 'label' properties, and are passed to add().
 *
 * @param {Function} source(request, response):
 *    Called with the autocomplete request, containing .term with the search term entered so far.
 *    The response callback must be called with a list of suggested items (with 'value' and
 *    'label' properties). The selected item is passed to add(). The caller should filter out
 *    items already selected if appropriate.
 * @param {koArray}  selected:
 *    KoArray of selected items.
 * @param {Function} itemCreateFunc:
 *    Called as `itemCreateFunc(item)` for each element of the `selected` array. Should return a
 *    single Node, or null or undefined to omit that node.
 * @param {Function} options.add(autoCompleteItem):
 *    Called to add a new item.
 * @param {Function} options.remove(item):
 *    Called to remove a selected item.
 * @param {Function} options.reorder(item, nextItem):
 *    Optional. Called to move item to just before nextItem (or to the end when nextItem is null).
 *    If omitted, items are not draggable. The callback must update the 'selected' array to
 *    match the UI. See koForm.draggableList for more details.
 * @param {String} options.hint:
 *    Optional. Text to display above the input if nothing is selected.
 */
function multiselect(source, selected, itemCreateFunc, options) {
  options = options || {};
  var noneSelected = ko.computed(() => selected.all().length === 0);
  var selector;
  var input;

  // Calls add on the item, closes the autocomplete and clears the input.
  function selectItem(item) {
    options.add(item);
    $(input).autocomplete("close");
    input.value = '';
  }

  // Searches for the item by label in the source and selects the first match.
  function searchItem(searchTerm) {
    source({ term: searchTerm }, resp => {
      var item = resp.find(respItem => respItem.label === searchTerm);
      if (item) { selectItem(item); }
    });
  }

  // Main selector dom with draggable list.
  selector = dom('div.multiselect',
    dom.autoDispose(noneSelected),
    dom('div.multiselect-selected',
      kf.draggableList(selected, item => itemCreateFunc(item), {
        drag_indicator: Boolean(options.reorder),
        removeButton: true,
        reorder: options.reorder,
        remove: options.remove
      }),
      kd.toggleClass('multiselect-empty', noneSelected),
      kd.maybe(noneSelected, () => dom('div.multiselect-hint', options.hint || ""))
    ),
    input = dom('input.multiselect-input',
      dom.on('focus', () => { $(input).autocomplete("search"); }),
      dom.on('change', () => { searchItem(input.value); })
    )
  );

  // Set up the auto-complete widget.
  $(input).autocomplete({
    source: source,
    minLength: 0,
    delay: 10,
    focus: () => false, // Keeps input empty on focus
    select: function(event, ui) {
      selectItem(ui.item);
      return false;
    }
  });

  return selector;
}
module.exports = multiselect;