this.MenuSearch = class {
  constructor(menuItems, options = {}) {
    this.locale = options.locale || AS?.OPTIONS?.locale || 'ru';
    this.mode = options.mode || 'OR';
    this.delay = options.delay || 250;

    this.indexedMenu = this.buildIndex(menuItems);

    this.searchDebounced = this.debounce(
      this.search.bind(this),
      this.delay
    );
  }

  set filterCondition(mode) {
    if (mode === 'AND' || mode === 'OR') this.mode = mode;
  }

  get filterCondition() {
    return this.mode;
  }

  normalize(str = '') {
    return str
      .toLowerCase()
      .replace(/[^\p{L}\p{N}\s]/gu, ' ')
      .replace(/\s+/g, ' ')
      .trim();
  }

  getLocalizedTitle(item) {
    const t = item.translations?.find(t => t.locale === this.locale);
    return t?.value || item.title || '';
  }

  buildIndex(items) {
    return items.map(item => {
      const indexed = {
        ...item,
        _searchIndex: this.normalize(
          `${this.getLocalizedTitle(item)} ${item.code || ''}`
        )
      };

      if (item.is_group && item.items) {
        indexed.items = this.buildIndex(item.items);
      }

      return indexed;
    });
  }


  calcScore(index, words, mode) {
    let score = 0;

    for (const w of words) {
      if (index.includes(` ${w} `)) score += 2;
      if (index.startsWith(w) || index.includes(` ${w}`)) score += 3;
    }

    if (mode === 'AND' && words.every(w => index.includes(w))) {
      score += 5;
    }

    if (index.length < 40) score += 1;

    return score;
  }

  search(query, modeOverride) {
    if (!query) return [];

    const mode = modeOverride || this.mode;
    const words = this.normalize(query).split(' ');
    const result = [];

    const match =
      mode === 'AND'
        ? index => words.every(w => index.includes(w))
        : index => words.some(w => index.includes(w));

    const walk = (list) => {
      for (const item of list) {
        if (!item.is_group && match(item._searchIndex)) {
          result.push({
            ...item,
            _score: this.calcScore(item._searchIndex, words, mode)
          });
        }

        if (item.is_group && item.items) {
          walk(item.items);
        }
      }
    };

    walk(this.indexedMenu);

    return result.sort((a, b) => b._score - a._score);
  }


  debounce(fn, delay) {
    let t;
    return (query, mode) => {
      clearTimeout(t);
      t = setTimeout(() => fn(query, mode), delay);
    };
  }
}


/* ПРИМЕР ИСПОЛЬЗОВАНИЯ

const search = new MenuSearch(menuItems, { mode: 'OR' }); //инициализация компонента

search.filterCondition = 'AND'; // установка условия сравнения

const result = search.search('заявление кжс'); //поиск

//можно условие передавать в поиск
search.search('заявление кжс', 'OR');
search.search('заявление кжс', 'AND');

//пример получения результата поиска с задержкой
search.searchDebounced = search.debounce((value) => {
  const result = search.search(value);
  console.log(result);
}, 250);

searchInput.addEventListener('input', e => {
  search.searchDebounced(e.target.value);
});
*/