/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This Source Code Form is "Incompatible With Secondary Licenses", as
 * defined by the Mozilla Public License, v. 2.0. */

// global

var Dom = YAHOO.util.Dom;
var Event = YAHOO.util.Event;
var History = YAHOO.util.History;

var guided = {
  _currentStep: '',
  _defaultStep: 'product',
  currentUser: '',
  openStates: [],
  updateStep: true,

  setStep: function(newStep, noSetHistory) {
    // initialize new step
    this.updateStep = true;
    switch(newStep) {
      case 'webdev':
        webdev.onShow();
        break;
      case 'product':
        product.onShow();
        break;
      case 'otherProducts':
        otherProducts.onShow();
        break;
      case 'dupes':
        dupes.onShow();
        break;
      case 'bugForm':
        bugForm.onShow();
        break;
      default:
        guided.setStep(this._defaultStep);
        return;
    }

    if (!this.updateStep)
        return;

    // change visibility of _step div
    if (this._currentStep)
      Dom.addClass(this._currentStep + '_step', 'hidden');
    this._currentStep = newStep;
    Dom.removeClass(this._currentStep + '_step', 'hidden');

    // scroll to top of page to mimic real navigation
    scroll(0,0);

    // update history
    if (History && !noSetHistory) {
      History.navigate('h', newStep + '|' + product.getName() +
                            (product.getPreselectedComponent() ? '|' + product.getPreselectedComponent() : '')
      );
    }
  },

  init: function(conf) {
    // init history manager
    if (conf.webdev) {
      this._defaultStep = 'webdev';
      this.webdev = true;
    }
    try {
      History.register('h', History.getBookmarkedState('h') || this._defaultStep, this._onStateChange);
      History.initialize("yui-history-field", "yui-history-iframe");
      History.onReady(function () {
        guided._onStateChange(History.getCurrentState('h'), true);
      });
    } catch(err) {
      History = false;
    }

    // init steps
    webdev.onInit();
    product.onInit();
    dupes.onInit();
    bugForm.onInit();
  },

  _onStateChange: function(state, noSetHistory) {
    state = state.split('|');
    product.setName(state[1] || '');
    product.setPreselectedComponent(state[2] || '');
    guided.setStep(state[0], noSetHistory);
  },

  setAdvancedLink: function() {
    var href = `${BUGZILLA.config.basepath}enter_bug.cgi?format=__default__` +
               `&product=${encodeURIComponent(product.getName())}` +
               `&short_desc=${encodeURIComponent(dupes.getSummary())}`;
    Dom.get('advanced_img').href = href;
    Dom.get('advanced_link').href = href;
  }
};

// webdev step

var webdev = {
    details: false,

    onInit: function () { },

    onShow: function () { }
};

// product step

var product = {
  details: false,
  _loaded: '',
  _preselectedComponent: '',

  onInit: function() { },

  onShow: function() {
    Dom.removeClass('advanced', 'hidden');
  },

  select: function(productName, componentName) {
    var prod = products[productName];

    // called when a product is selected
    if (componentName) {
      this.setPreselectedComponent(componentName);
      if (prod && prod.defaultComponent) {
        prod.originalDefaultComponent = prod.originalDefaultComponent || prod.defaultComponent;
        prod.defaultComponent = componentName;
      }
    }
    else {
      if (prod && prod.defaultComponent && prod.originalDefaultComponent) {
        prod.defaultComponent = prod.originalDefaultComponent;
      }
    }
    this.setName(productName);
    dupes.reset();
    guided.setStep('dupes');
  },

  getName: function() {
    return Dom.get('product').value;
  },

  getPreselectedComponent: function() {
    return this._preselectedComponent;
  },

  setPreselectedComponent: function(value) {
    this._preselectedComponent = value;
  },

  _getNameAndRelated: function() {
    var result = [];

    var name = this.getName();
    result.push(name);

    if (products[name] && products[name].related) {
      for (var i = 0, n = products[name].related.length; i < n; i++) {
        result.push(products[name].related[i]);
      }
    }

    return result;
  },

  setName: async function(productName) {
    if (productName == this.getName() && this.details)
      return;

    // display the product name
    Dom.get('product').value = productName;
    Dom.get('product_label').innerHTML = productName.htmlEncode();
    Dom.get('dupes_product_name').innerHTML = productName.htmlEncode();
    Dom.get('list_comp').href = `${BUGZILLA.config.basepath}describecomponents.cgi?` +
                                `product=${encodeURIComponent(productName)}`;
    guided.setAdvancedLink();

    if (productName == '') {
      Dom.addClass("product_support", "hidden");
      return;
    }

    // show support message
    if (products[productName] && products[productName].support) {
      Dom.get("product_support_message").innerHTML = products[productName].support;
      Dom.removeClass("product_support", "hidden");
    } else {
      Dom.addClass("product_support", "hidden");
    }

    // show/hide component selection row
    if (products[productName] && products[productName].noComponentSelection || guided.webdev) {
      if (!Dom.hasClass('componentTR', 'hidden')) {
        Dom.addClass('componentTR', 'hidden');
      }
    } else {
      if (Dom.hasClass('componentTR', 'hidden')) {
        Dom.removeClass('componentTR', 'hidden');
      }
    }

    if (this._loaded == productName)
      return;

    // grab the product information
    this.details = false;
    this._loaded = productName;

    try {
      const { products } = await Bugzilla.API.get('product', {
        names: [productName],
        exclude_fields: ['internals', 'milestones', 'components.flag_types'],
      });

      if (products.length) {
        product.details = products[0];
        bugForm.onProductUpdated();
      } else {
        document.location.href = `${BUGZILLA.config.basepath}enter_bug.cgi?format=guided`;
      }
    } catch ({ message }) {
      this._loaded = '';
      product.details = false;
      bugForm.onProductUpdated();
      alert(`Failed to retrieve components for product "${productName}"\n\n${message}`);
    }
  }
};

// other products step

var otherProducts = {
  onInit: function() { },

  onShow: function() {
    Dom.removeClass('advanced', 'hidden');
  }
};

// duplicates step

var dupes = {
  _dataTable: null,
  _dataTableColumns: null,
  _elSummary: null,
  _elSearch: null,
  _elList: null,
  _currentSearchQuery: '',

  onInit: function() {
    this._elSummary = Dom.get('dupes_summary');
    this._elSearch = Dom.get('dupes_search');
    this._elList = Dom.get('dupes_list');

    Event.onBlur(this._elSummary, this._onSummaryBlur);
    Event.addListener(this._elSummary, 'input', this._onSummaryBlur);
    Event.addListener(this._elSummary, 'keydown', this._onSummaryKeyDown);
    Event.addListener(this._elSummary, 'keyup', this._onSummaryKeyUp);
    Event.addListener(this._elSearch, 'click', this._doSearch);
  },

  setLabels: function(labels) {
    this._dataTableColumns = [
      { key: "id", label: labels.id, formatter: this._formatId },
      { key: "summary", label: labels.summary, formatter: "text" },
      { key: "component", label: labels.component, formatter: "text" },
      { key: "status", label: labels.status, formatter: this._formatStatus },
      { key: "update_token", label: '', formatter: this._formatCc }
    ];
  },

  _initDataTable: function() {
    this._dataTable = new YAHOO.widget.DataTable(
      'dupes_list',
      this._dataTableColumns,
      new YAHOO.util.LocalDataSource([]), // Dummy data source
      {
        initialLoad: false,
        MSG_EMPTY: 'No similar issues found.',
        MSG_ERROR: 'An error occurred while searching for similar issues,' +
          ' please try again.'
      }
    );
  },

  _formatId: function(el, oRecord, oColumn, oData) {
    el.innerHTML = `<a href="${BUGZILLA.config.basepath}show_bug.cgi?id=${oData}" target="_blank">${oData}</a>`;
  },

  _formatStatus: function(el, oRecord, oColumn, oData) {
    var resolution = oRecord.getData('resolution');
    var bug_status = display_value('bug_status', oData);
    if (resolution) {
      el.innerHTML = bug_status + ' ' +
        display_value('resolution', resolution);
    } else {
      el.innerHTML = bug_status;
    }
  },

  _formatCc: function(el, oRecord, oColumn, oData) {
   var cc = oRecord.getData('cc');
    var isCCed = false;
    for (var i = 0, n = cc.length; i < n; i++) {
      if (cc[i] == guided.currentUser) {
        isCCed = true;
        break;
      }
    }
    dupes._buildCcHTML(el, oRecord.getData('id'), oRecord.getData('status'),
      isCCed);
  },

  _buildCcHTML: function(el, id, bugStatus, isCCed) {
    while (el.childNodes.length > 0)
      el.removeChild(el.firstChild);

    var isOpen = false;
    for (var i = 0, n = guided.openStates.length; i < n; i++) {
      if (guided.openStates[i] == bugStatus) {
        isOpen = true;
        break;
      }
    }

    if (!isOpen && !isCCed) {
      // you can't cc yourself to a closed bug here
      return;
    }

    var button = document.createElement('button');
    button.setAttribute('type', 'button');
    if (isCCed) {
      button.innerHTML = 'Stop&nbsp;following';
      button.onclick = function() {
        dupes.updateFollowing(el, id, bugStatus, button, false); return false;
      };
    } else {
      button.innerHTML = 'Follow&nbsp;bug';
      button.onclick = function() {
        dupes.updateFollowing(el, id, bugStatus, button, true); return false;
      };
    }
    el.appendChild(button);
  },

  updateFollowing: async function(el, bugID, bugStatus, button, follow) {
    button.disabled = true;
    button.innerHTML = 'Updating...';

    var ccObject;
    if (follow) {
      ccObject = { add: [ guided.currentUser ] };
    } else {
      ccObject = { remove: [ guided.currentUser ] };
    }

    try {
      await Bugzilla.API.put(`bug/${bugID}`, { ids: [bugID], cc: ccObject });
      dupes._buildCcHTML(el, bugID, bugStatus, follow);
    } catch ({ message }) {
      dupes._buildCcHTML(el, bugID, bugStatus, !follow);
      alert(`Update failed:\n\n${message}`);
    }
  },

  reset: function() {
    this._elSummary.value = '';
    Dom.addClass(this._elList, 'hidden');
    Dom.addClass('dupes_continue', 'hidden');
    this._elList.innerHTML = '';
    this._showProductSupport();
    this._currentSearchQuery = '';
    this._elSummary.focus();
  },

  _showProductSupport: function() {
    var elSupport = Dom.get('product_support_' +
      product.getName().replace(' ', '_').toLowerCase());
    var supportElements = Dom.getElementsByClassName('product_support');
    for (var i = 0, n = supportElements.length; i < n; i++) {
      if (supportElements[i] == elSupport) {
        Dom.removeClass(elSupport, 'hidden');
      } else {
        Dom.addClass(supportElements[i], 'hidden');
      }
    }
  },

  onShow: function() {
    this._showProductSupport();
    this._onSummaryBlur();

    // hide the advanced form and top continue button entry until
    // a search has happened
    Dom.addClass('advanced', 'hidden');
    Dom.addClass('dupes_continue_button_top', 'hidden');
    var prod = product.getName();
    if (products[prod] && products[prod].l10n) {
      Dom.removeClass('l10n_message', 'hidden');
      Dom.get('l10n_product').textContent = product.getName();
      Dom.get('l10n_link').onclick = function () {
        product.select('Mozilla Localizations');
      };
    }
    else {
      Dom.addClass('l10n_message', 'hidden');
    }

    if (!this._elSearch.disabled && this.getSummary().length >= 4) {
      // do an immediate search after a page refresh if there's a query
      this._doSearch();

    } else {
      // prepare for a search
      this.reset();
    }
  },

  _onSummaryBlur: function() {
    dupes._elSearch.disabled = dupes._elSummary.value == '';
    guided.setAdvancedLink();
  },

  _onSummaryKeyDown: function(e) {
    // map <enter> to doSearch()
    if (e && (e.keyCode == 13)) {
      dupes._doSearch();
      Event.stopPropagation(e);
    }
  },

  _onSummaryKeyUp: function(e) {
    // disable search button until there's a query
    dupes._elSearch.disabled = dupes._elSummary.value.trim() == '';
  },

  _doSearch: async function() {
    if (dupes.getSummary().length < 4) {
      alert('The summary must be at least 4 characters long.');
      return;
    }
    dupes._elSummary.blur();

    // don't query if we already have the results (or they are pending)
    if (dupes._currentSearchQuery == dupes.getSummary())
      return;
    dupes._currentSearchQuery = dupes.getSummary();

    // initialize the datatable as late as possible
    dupes._initDataTable();

    try {
      // run the search
      Dom.removeClass(dupes._elList, 'hidden');

      dupes._dataTable.showTableMessage(
        'Searching for similar issues...&nbsp;&nbsp;&nbsp;' +
        `<img src="${BUGZILLA.config.basepath}extensions/GuidedBugEntry/web/images/throbber.gif"` +
        ' width="16" height="11">',
        YAHOO.widget.DataTable.CLASS_LOADING
      );

      Dom.get('dupes_continue_button_top').disabled = true;
      Dom.get('dupes_continue_button_bottom').disabled = true;
      Dom.removeClass('dupes_continue', 'hidden');

      let data;

      try {
        const { bugs } = await Bugzilla.API.get('bug/possible_duplicates', {
          product: product._getNameAndRelated(),
          summary: dupes.getSummary(),
          limit: 12,
          include_fields: ['id', 'summary', 'status', 'resolution', 'update_token', 'cc', 'component'],
        });

        data = { results: bugs };
      } catch (ex) {
        dupes._currentSearchQuery = '';
        data = { error: true };
      }

      Dom.removeClass('advanced', 'hidden');
      Dom.removeClass('dupes_continue_button_top', 'hidden');
      Dom.get('dupes_continue_button_top').disabled = false;
      Dom.get('dupes_continue_button_bottom').disabled = false;
      dupes._dataTable.onDataReturnInitializeTable('', data);
    } catch(err) {
      if (console)
        console.error(err.message);
    }
  },

  getSummary: function() {
    var summary = this._elSummary.value.trim();
    // work around chrome bug
    if (summary == dupes._elSummary.getAttribute('placeholder')) {
      return '';
    } else {
      return summary;
    }
  }
};

// bug form step

var bugForm = {
  _visibleHelpPanel: null,
  _mandatoryFields: [],
  _conditionalDetails: [
    { check: function () { return product.getName() == 'Firefox'; }, id: 'firefox_for_android_row' }
  ],

  onInit: function() {
    var user_agent = navigator.userAgent;
    Dom.get('user_agent').value = navigator.userAgent;
    Event.addListener(Dom.get('short_desc'), 'blur', function() {
      Dom.get('dupes_summary').value = Dom.get('short_desc').value;
      guided.setAdvancedLink();
    });
  },

  onShow: function() {
    // check for a forced format
    var productName = product.getName();
    var visibleCount = 0;
    if (products[productName] && products[productName].format) {
        Dom.addClass('advanced', 'hidden');
        document.location.href = `${BUGZILLA.config.basepath}enter_bug.cgi?` +
                                 `format=${encodeURIComponent(products[productName].format)}` +
                                 `&product=${encodeURIComponent(productName)}` +
                                 `&short_desc=${encodeURIComponent(dupes.getSummary())}`;
        guided.updateStep = false;
        return;
    }
    Dom.removeClass('advanced', 'hidden');
    // default the summary to the dupes query
    Dom.get('short_desc').value = dupes.getSummary();
    this.resetSubmitButton();
    if (Dom.get('component_select').length == 0)
      this.onProductUpdated();
    this.onFileChange();
    for (var i = 0, n = this._mandatoryFields.length; i < n; i++) {
      Dom.removeClass(this._mandatoryFields[i], 'missing');
    }

    this._conditionalDetails.forEach(function (cond) {
      if (cond.check()) {
        visibleCount++;
        Dom.removeClass(cond.id, 'hidden');
      }
      else {
        Dom.addClass(cond.id, 'hidden');
      }
    });
    if (visibleCount > 0) {
      Dom.removeClass('details', 'hidden');
      Dom.removeClass('submitTR', 'even');
    }
    else {
      Dom.addClass('details', 'hidden');
      Dom.addClass('submitTR', 'even');
    }
  },

  resetSubmitButton: function() {
    Dom.get('submit').disabled = false;
    Dom.get('submit').value = 'Submit Bug';
  },

  onProductUpdated: function() {
    var productName = product.getName();

    // init
    var elComponents = Dom.get('component_select');
    Dom.addClass('component_description', 'hidden');
    elComponents.options.length = 0;

    var elVersions = Dom.get('version_select');
    elVersions.length = 0;

    // product not loaded yet, bail out
    if (!product.details) {
      Dom.addClass('versionTH', 'hidden');
      Dom.addClass('versionTD', 'hidden');
      Dom.get('productTD').colSpan = 2;
      Dom.get('submit').disabled = true;
      return;
    }
    Dom.get('submit').disabled = false;

    // filter components
    if (products[productName] && products[productName].componentFilter) {
        product.details.components = products[productName].componentFilter(product.details.components);
    }

    // build components

    var elComponent = Dom.get('component');
    if (products[productName] && products[productName].noComponentSelection || guided.webdev) {
      elComponent.value = products[productName].defaultComponent;
      bugForm._mandatoryFields = [ 'short_desc', 'version_select' ];
    } else {
      bugForm._mandatoryFields = [ 'short_desc', 'component_select', 'version_select' ];

      // check for the default component
      var defaultRegex;
      if (product.getPreselectedComponent()) {
        defaultRegex = new RegExp('^' + quoteMeta(product.getPreselectedComponent()) + '$', 'i');
      } else if(products[productName] && products[productName].defaultComponent) {
        defaultRegex = new RegExp('^' + quoteMeta(products[productName].defaultComponent) + '$', 'i');
      } else {
        defaultRegex = new RegExp('General', 'i');
      }

      var preselectedComponent = false;
      var i, n;
      var component;
      for (i = 0, n = product.details.components.length; i < n; i++) {
        component = product.details.components[i];
        if (component.is_active == '1') {
          if (defaultRegex.test(component.name)) {
            preselectedComponent = component.name;
            break;
          }
        }
      }

      // if there isn't a default component, default to blank
      if (!preselectedComponent) {
        elComponents.options[elComponents.options.length] = new Option('', '');
      }

      // build component select
      for (i = 0, n = product.details.components.length; i < n; i++) {
        component = product.details.components[i];
        if (component.is_active == '1') {
          elComponents.options[elComponents.options.length] =
            new Option(component.name, component.name);
        }
      }

      var validComponent = false;
      for (i = 0, n = elComponents.options.length; i < n && !validComponent; i++) {
        if (elComponents.options[i].value == elComponent.value)
          validComponent = true;
      }
      if (!validComponent)
        elComponent.value = '';
      if (elComponent.value == '' && preselectedComponent)
        elComponent.value = preselectedComponent;
      if (elComponent.value != '') {
        elComponents.value = elComponent.value;
        this.onComponentChange(elComponent.value);
      }

    }

    // build versions
    var defaultVersion = '';
    var currentVersion = Dom.get('version').value;
    for (i = 0, n = product.details.versions.length; i < n; i++) {
      var version = product.details.versions[i];
      if (version.is_active == '1') {
        elVersions.options[elVersions.options.length] =
          new Option(version.name, version.name);
        if (currentVersion == version.name)
          defaultVersion = version.name;
      }
    }

    if (!defaultVersion) {
      // try to detect version on a per-product basis
      if (products[productName] && products[productName].version) {
        var detectedVersion = products[productName].version();
        var options = elVersions.options;
        for (i = 0, n = options.length; i < n; i++) {
          if (options[i].value == detectedVersion) {
            defaultVersion = detectedVersion;
            break;
          }
        }
      }
    }

    if (elVersions.length > 1) {
      // more than one version, show select
      Dom.get('productTD').colSpan = 1;
      Dom.removeClass('versionTH', 'hidden');
      Dom.removeClass('versionTD', 'hidden');

    } else {
      // if there's only one version, we don't need to ask the user
      Dom.addClass('versionTH', 'hidden');
      Dom.addClass('versionTD', 'hidden');
      Dom.get('productTD').colSpan = 2;
      defaultVersion = elVersions.options[0].value;
    }

    if (defaultVersion) {
      elVersions.value = defaultVersion;

    } else {
      // no default version, select an empty value to force a decision
      var opt = new Option('', '');
      try {
        // standards
        elVersions.add(opt, elVersions.options[0]);
      } catch(ex) {
        // IE only
        elVersions.add(opt, 0);
      }
      elVersions.value = '';
    }
    bugForm.onVersionChange(elVersions.value);

    // Set default Platform, OS and Security Group
    // Skip if the default value is empty = auto-detect
    const { default_platform, default_op_sys, default_security_group } = product.details;

    if (default_platform) {
      document.querySelector('#rep_platform').value = default_platform;
    }

    if (default_op_sys) {
      document.querySelector('#op_sys').value = default_op_sys;
    }

    if (default_security_group) {
      document.querySelector('#groups').value = default_security_group;
    }
  },

  onComponentChange: function(componentName) {
    // show the component description
    Dom.get('component').value = componentName;
    var elComponentDesc = Dom.get('component_description');
    elComponentDesc.innerHTML = '';
    for (var i = 0, n = product.details.components.length; i < n; i++) {
      var component = product.details.components[i];
      if (component.name == componentName) {
        elComponentDesc.innerHTML = component.description;
        break;
      }
    }
    Dom.removeClass(elComponentDesc, 'hidden');
  },

  onVersionChange: function(version) {
    Dom.get('version').value = version;
  },

  onFileChange: function() {
    // toggle ui enabled when a file is uploaded or cleared
    var elFile = Dom.get('data');
    var elReset = Dom.get('reset_data');
    var elDescription = Dom.get('data_description');
    var filename = bugForm._getFilename();
    if (filename) {
      elReset.disabled = false;
      elDescription.value = filename;
      elDescription.disabled = false;
      Dom.removeClass('reset_data', 'hidden');
      Dom.removeClass('data_description_tr', 'hidden');
    } else {
      elReset.disabled = true;
      elDescription.value = '';
      elDescription.disabled = true;
      Dom.addClass('reset_data', 'hidden');
      Dom.addClass('data_description_tr', 'hidden');
    }
  },

  onFileClear: function() {
    Dom.get('data').value = '';
    this.onFileChange();
    return false;
  },

  _getFilename: function() {
    var filename = Dom.get('data').value;
    if (!filename)
      return '';
    filename = filename.replace(/^.+[\\\/]/, '');
    return filename;
  },

  _mandatoryMissing: function() {
    var result = new Array();
    for (var i = 0, n = this._mandatoryFields.length; i < n; i++ ) {
      var id = this._mandatoryFields[i];
      var el = Dom.get(id);
      var value;

      if (el.type.toString() == "checkbox") {
        value = el.checked;
      } else {
        value = el.value.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
        el.value = value;
      }

      if (value == '') {
        Dom.addClass(id, 'missing');
        result.push(id);
      } else {
        Dom.removeClass(id, 'missing');
      }
    }
    return result;
  },

  validate: function() {

    // check mandatory fields

    var missing = bugForm._mandatoryMissing();
    if (missing.length) {
      var message = 'The following field' +
        (missing.length == 1 ? ' is' : 's are') + ' required:\n\n';
      for (var i = 0, n = missing.length; i < n; i++ ) {
        var id = missing[i];
        if (id == 'short_desc')       message += '  Summary\n';
        if (id == 'component_select') message += '  Component\n';
        if (id == 'version_select')   message += '  Version\n';
      }
      alert(message);
      return false;
    }

    if (Dom.get('data').value && !Dom.get('data_description').value)
      Dom.get('data_description').value = bugForm._getFilename();

    Dom.get('submit').disabled = true;
    Dom.get('submit').value = 'Submitting Bug...';

    return true;
  },

  _initHelp: function(el) {
    var help_id = el.getAttribute('helpid');
    if (!el.panel) {
      if (!el.id)
        el.id = help_id + '_parent';
      el.panel = new YAHOO.widget.Panel(
        help_id,
        {
          width: "320px",
          visible: false,
          close: false,
          context: [el.id, 'tl', 'tr', null, [5, 0]]
        }
      );
      el.panel.render();
      Dom.removeClass(help_id, 'hidden');
    }
  },

  showHelp: function(el) {
    this._initHelp(el);
    if (this._visibleHelpPanel)
      this._visibleHelpPanel.hide();
    el.panel.show();
    this._visibleHelpPanel = el.panel;
  },

  hideHelp: function(el) {
    if (!el.panel)
      return;
    if (this._visibleHelpPanel)
      this._visibleHelpPanel.hide();
    el.panel.hide();
    this._visibleHelpPanel = null;
  }
};

function quoteMeta(value) {
  return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
