/* global debounce */// toolbox.js
/* global installed */// manage.js
/* global
  $$
  $
  $create
  $isTextInput
  animateElement
  scrollElementIntoView
*/// dom.js
'use strict';

(async () => {
  await require(['/manage/incremental-search.css']);
  let prevText, focusedLink, focusedEntry;
  let prevTime = performance.now();
  let focusedName = '';
  const input = $create('textarea', {
    id: 'incremental-search',
    spellcheck: false,
    attributes: {tabindex: -1},
    oninput: incrementalSearch,
  });
  replaceInlineStyle({
    opacity: '0',
  });
  document.body.appendChild(input);
  window.on('keydown', maybeRefocus, true);

  function incrementalSearch(event, immediately) {
    const {key} = event;
    if (!immediately) {
      debounce(incrementalSearch, 100, {}, true);
      return;
    }
    const direction = key === 'ArrowUp' ? -1 : key === 'ArrowDown' ? 1 : 0;
    const text = input.value.toLocaleLowerCase();
    if (direction) {
      event.preventDefault();
    }
    if (!text.trim() || !direction && (text === prevText || focusedName.startsWith(text))) {
      prevText = text;
      return;
    }
    let textAtPos = 1e6;
    let rotated;
    const entries = $('#message-box') ? $$('.injection-order-entry') : [...installed.children];
    const focusedIndex = entries.indexOf(focusedEntry);
    if (focusedIndex > 0) {
      if (direction > 0) {
        rotated = entries.slice(focusedIndex + 1).concat(entries.slice(0, focusedIndex + 1));
      } else if (direction < 0) {
        rotated = entries.slice(0, focusedIndex).reverse()
          .concat(entries.slice(focusedIndex).reverse());
      }
    }
    let found;
    for (const entry of rotated || entries) {
      if (entry.classList.contains('hidden')) continue;
      const name = entry.styleNameLC;
      const pos = name.indexOf(text);
      if (pos === 0) {
        found = entry;
        break;
      } else if (pos > 0 && (pos < textAtPos || direction)) {
        found = entry;
        textAtPos = pos;
        if (direction) {
          break;
        }
      }
    }
    if (found && found !== focusedEntry) {
      focusedEntry = found;
      focusedLink = $('a', found);
      focusedName = found.styleNameLC;
      scrollElementIntoView(found, {invalidMarginRatio: .25});
      animateElement(found, 'highlight-quick');
      replaceInlineStyle({
        width: focusedLink.offsetWidth + 'px',
        height: focusedLink.offsetHeight + 'px',
        opacity: '1',
      });
      focusedLink.prepend(input);
      input.focus();
      return true;
    }
  }

  function maybeRefocus(event) {
    if (event.altKey || event.metaKey) {
      return;
    }
    const modal = $('#message-box');
    if (modal && !modal.classList.contains('injection-order')) {
      return;
    }
    const inTextInput = $isTextInput(event.target);
    const {key, code, ctrlKey: ctrl} = event;
    // `code` is independent of the current keyboard language
    if ((code === 'KeyF' && ctrl && !event.shiftKey) ||
        (code === 'Slash' || key === '/') && !ctrl && !inTextInput) {
      // focus search field on "/" or Ctrl-F key
      event.preventDefault();
      if (!modal) $('#search').focus();
      return;
    }
    if (ctrl || inTextInput && event.target !== input) {
      return;
    }
    const time = performance.now();
    if (key.length === 1) {
      if (time - prevTime > 1000) {
        input.value = '';
      }
      // Space or Shift-Space is for page down/up
      if (key === ' ' && !input.value) {
        input.blur();
      } else {
        input.focus();
        prevTime = time;
      }
    } else
    if (key === 'Enter' && focusedLink) {
      focusedLink.dispatchEvent(new MouseEvent('click', {bubbles: true}));
    } else
    if ((key === 'ArrowUp' || key === 'ArrowDown') && !event.shiftKey &&
        time - prevTime < 5000 && incrementalSearch(event, true)) {
      prevTime = time;
    } else
    if (event.target === input) {
      (focusedLink || document.body).focus();
      input.value = '';
    }
  }

  function replaceInlineStyle(css) {
    for (const prop in css) {
      input.style.setProperty(prop, css[prop], 'important');
    }
  }
})();