# Source: src/js/views/selectUI/QueryFieldSelectView.js

    define([
        "jquery",
        "underscore",
        "backbone",
        "views/selectUI/SearchableSelectView",
        "collections/queryFields/QueryFields"
      ],
      function($, _, Backbone, SearchableSelect, QueryFields) {

        /**
         * @class QueryFieldSelect
         * @classdesc A select interface that allows the user to search for and
         * select metadata field(s).
         * @extends SearchableSelect
         * @constructor
         */
        var QueryFieldSelect = SearchableSelect.extend(
          /** @lends QueryFieldSelectView.prototype */
          {
            /**
             * The type of View this is
             * @type {string}
             */
            type: "QueryFieldSelect",

            /**
             * className - Returns the class names for this view element
             *
             * @return {string}  class names
             */
            className: SearchableSelect.prototype.className + " query-field-select",

            /**
             * Text to show in the input field before any value has been entered
             * @type {string}
             */
            placeholderText: "Search for or select a field",

            /**
             * Label for the input element
             * @type {string}
             */
            inputLabel: "Select one or more metadata fields to query",

            /**
             * Whether to allow users to select more than one value
             * @type {boolean}
             */
            allowMulti: true,

            /**
             * Setting to true gives users the ability to add their own options that
             * are not listed in this.options. This can work with either single
             * or multiple search select dropdowns
             * @type {boolean}
             */
            allowAdditions: false,

            /**
             * When items are grouped within categories, how to display the items
             * within each category? Select one of the following options:
             *  list: display the items in a traditional, non-interactive list below
             *        category titles
             *  popout: initially show only a list of category titles, and popout
             *          a submenu on the left or right when the user hovers over
             *          or touches a category (can lead to the sub-menu being hidden
             *          on mobile devices if the element is wide)
             *  accordion: initially show only a list of category titles, and expand
             *            the list of items below each category when a user clicks
             *            on the category title, much like an "accordion" element.
             * @type {string} set to "list", "popout", or "accordion"
             */
            submenuStyle: "accordion",

            /**
             * A list of query fields names to exclude from the list of options
             * @type {string[]}
             */
            excludeFields: [],

            /**
             * A list of query fields names to display at the top of the menu, above
             * all other category headers
             */
            commonFields: ["text"],

            /**
             * Whether or not to exclude fields which are not searchable. Set to
             * false to keep query fields that are not seachable in the returned list
             * @type {boolean}
             */
            excludeNonSearchable: true,

            /**
             * Creates a new QueryFieldSelectView
             * @param {Object} options - A literal object with options to pass to the view
             */
            initialize: function(options){
              try {
                // Ensure the query fields are cached
                if ( typeof MetacatUI.queryFields === "undefined" ) {
                  MetacatUI.queryFields = new QueryFields();
                  MetacatUI.queryFields.fetch();
                }
                SearchableSelect.prototype.initialize.call(this, options);
              } catch (e) {
                console.log("Failed to initialize a Query Field Select View, error message: " + e);
              }
            },

            /**
             * Render the view
             *
             * @return {SeachableSelect}  Returns the view
             */
            render: function(){

              try {

                var view = this;

                // Ensure the query fields are cached for the Query Field Select
                // View and the Query Rule View
                if ( typeof MetacatUI.queryFields === "undefined" || MetacatUI.queryFields.length === 0 ) {
                  MetacatUI.queryFields = new QueryFields();
                  this.listenToOnce(MetacatUI.queryFields, "sync", this.render)
                  MetacatUI.queryFields.fetch();
                  return
                }

                // Convert the queryFields collection to an object formatted for the
                // SearchableSelect view.
                var fieldsJSON = MetacatUI.queryFields.toJSON();

                // Move common fields to the top of the menu, outside of any
                // category headers, so that they are easy to find
                if(this.commonFields.length){
                  this.commonFields.forEach(function(commonFieldName){
                    var i = _.findIndex(fieldsJSON, { name: commonFieldName});
                    if(i>0){
                      // If the category name is an empty string, no header will
                      // be created in the menu
                      fieldsJSON[i].category = ""
                      // The min categoryOrder in the QueryFields collection is 1
                      fieldsJSON[i].categoryOrder = 0
                      fieldsJSON[i].icon = "star"
                    }
                  });
                }

                // Filter out non-searchable fields (if option is true),
                // and fields that should be excluded
                var processedFields = _(fieldsJSON)
                  .chain()
                  .sortBy("categoryOrder")
                  .filter(
                    function(filter){
                      if(this.excludeNonSearchable){
                        if(filter.searchable !== "true"){
                          return false
                        }
                      }
                      if(this.excludeFields && this.excludeFields.length){
                        if(this.excludeFields.includes(filter.name)){
                          return false
                        }
                      }
                      return true
                    }, this
                  )
                  .map(view.fieldToOption)
                  .groupBy("categoryOrder")
                  .value();

                // Rename the grouped categories
                for (const [key, value] of Object.entries(processedFields)) {
                  processedFields[value[0].category] = value;
                  delete processedFields[key];
                }

                // Set the formatted fields on the view
                this.options = processedFields;

                SearchableSelect.prototype.render.call(this);

              } catch (e) {
                console.log("Failed to render a Query Field Select View, error message: " + e);
              }
            },

            /**
             * fieldToOption - Converts an object that represents a QueryField model
             * in the format specified by the SearchableSelectView.options
             *
             * @param  {object} field An object with properties corresponding to a QueryField model
             * @return {object}       An object in the format specified by SearchableSelectView.options
             */
            fieldToOption: function(field) {
               return {
                label: field.label ? field.label : field.name,
                value: field.name,
                description: field.friendlyDescription ? field.friendlyDescription : field.description,
                icon: field.icon,
                category: field.category,
                categoryOrder: field.categoryOrder,
                type: field.type
              };
            },

            /**
             * addTooltip - Add a tooltip to a given element using the description
             * in the options object that's set on the view.
             * This overwrites the prototype addTooltip function so that we can use
             * popovers with more details for query select fields.
             *
             * @param  {HTMLElement} element The HTML element a tooltip should be added
             * @param  {string} position how to position the tooltip - top | bottom | left | right
             * @return {jQuery} The element with a tooltip wrapped by jQuery
             */
            addTooltip: function(element, position = "bottom"){

              if(!element){
                return
              }

              // Find the description in the options object, using the data-value
              // attribute set in the template. The data-value attribute is either
              // the label, or the value, depending on if a value is provided.
              var valueOrLabel = $(element).data("value"),
                  opt = _.chain(this.options)
                              .values()
                              .flatten()
                              .find(function(option){
                                return option.label == valueOrLabel || option.value == valueOrLabel
                              })
                              .value()

              if(!opt){
                return
              }

              var contentEl = $(document.createElement("div")),
                  titleEl = $("<div>" + opt.label + "</div>"),
                  valueEl = $("<code class='pull-right'>" + opt.value + "</code>"),
                  typeEl = $("<span class='muted pull-right'><b>Type: " + opt.type + "</b></span>"),
                  descriptionEl = $("<p>" + opt.description + "</p>");

                titleEl.append(valueEl);
                contentEl.append(descriptionEl, typeEl)

              $(element).popover({
                title: titleEl,
                content: contentEl,
                html: true,
                trigger: "hover",
                placement: position,
                container: "body",
                delay: {
                  show: 1100,
                  hide: 50
                }
              })
              .on("show.bs.popover",
                function(){
                  var $el = $(this);
                  // Allow time for the popup to be added to the DOM
                  setTimeout(function () {
                    // Then add some css rules, and a special class to identify
                    // these popups if they need to be removed.
                    $el.data('popover').$tip
                      .css({
                        "maxWidth": "400px",
                        "pointerEvents" : "none"
                      })
                      .addClass("search-select-tooltip");
                  }, 10);
              });

              return $(element)

            },

            /**
             * isValidOption - Checks if a value is one of the values given in view.options
             *
             * @param  {string} value The value to check
             * @return {boolean}      returns true if the value is one of the values given in view.options
             */
            isValidOption: function(value){

              try {
                var view = this;

                // First check if the value is one of the fields that's excluded.
                if(view.excludeFields.includes(value)){
                  // If it is, then add it to the list of options
                  var newField = MetacatUI.queryFields.findWhere({
                    name: value
                  });
                  if(newField){
                    newField = view.fieldToOption(newField.toJSON());
                  }
                  view.options[newField.category].push(newField);
                  view.updateMenu();
                  // Make sure the new menu is attached before updating the selections
                  setTimeout(function () {
                    view.changeSelection(view.selected, silent = true);
                  }, 25);
                  return true
                } else {
                  var isValid = SearchableSelect.prototype.isValidOption.call(view, value);
                  return isValid
                }
              } catch (e) {
                console.log("Failed to check if option is valid in a Query Field Select View, error message: " + e);
              }

            },

          });
          return QueryFieldSelect;
      });