"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.SearchState = void 0;
exports.enableSearch = enableSearch;
require("codemirror/addon/search/searchcursor");
require("codemirror/addon/search/matchesonscrollbar");
require("codemirror/addon/search/jump-to-line");
class SearchState {}

/**
 * Following function is heavily based on original CodeMirror's search addon
 * with exception of `replace` functionality, standard dialog and code formatting.
 * https://github.com/codemirror/CodeMirror/blob/master/addon/search/search.js
 */
exports.SearchState = SearchState;
function enableSearch(codeMirror) {
  function searchOverlay(query, caseInsensitive) {
    let tempQuery;
    if (typeof query === 'string') {
      tempQuery = new RegExp(
      // eslint-disable-next-line no-useless-escape
      query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), caseInsensitive ? 'gi' : 'g');
    } else if (!query.global) {
      tempQuery = new RegExp(query.source, query.ignoreCase ? 'gi' : 'g');
    }
    return {
      token: stream => {
        tempQuery.lastIndex = stream.pos;
        const match = tempQuery.exec(stream.string);
        if (match && match.index === stream.pos) {
          stream.pos += match[0].length || 1;
          return 'searching';
        }
        if (match) {
          stream.pos = match.index;
        } else {
          stream.skipToEnd();
        }
      }
    };
  }
  function getSearchState(cm) {
    cm.state.search = cm.state.search || new SearchState();
    return cm.state.search;
  }
  function queryCaseInsensitive(query) {
    return typeof query === 'string' && query === query.toLowerCase();
  }
  function getSearchCursor(cm, query, pos) {
    // Heuristic: if the query string is all lowercase, do a case insensitive search.
    return cm.getSearchCursor(query, pos, queryCaseInsensitive(query));
  }
  function parseString(string) {
    return string.replace(/\\([nrt\\])/g, function (match, ch) {
      if (ch === 'n') {
        return '\n';
      }
      if (ch === 'r') {
        return '\r';
      }
      if (ch === 't') {
        return '\t';
      }
      if (ch === '\\') {
        return '\\';
      }
      return match;
    });
  }
  function parseQuery(query) {
    let parsedQuery = '';
    const isRE = query.match(/^\/(.*)\/([a-z]*)$/);
    if (isRE) {
      try {
        parsedQuery = new RegExp(isRE[1], isRE[2].indexOf('i') === -1 ? '' : 'i');
        // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-empty
      } catch (e) {} // Not a regular expression after all, do a string search
    } else {
      parsedQuery = parseString(query);
    }
    if (typeof parsedQuery === 'string' ? parsedQuery === '' : parsedQuery.test('')) {
      parsedQuery = /x^/;
    }
    return parsedQuery;
  }
  function startSearch(cm, state, query) {
    state.queryText = query;
    state.query = parseQuery(query);
    cm.removeOverlay(state.overlay);
    state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
    cm.addOverlay(state.overlay);
    if (cm.showMatchesOnScrollbar) {
      if (state.annotate) {
        state.annotate.clear();
        state.annotate = null;
      }
      state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));
    }
  }
  function doSearch(cm, rev) {
    const state = getSearchState(cm);
    if (state.query) {
      return findNext(cm, rev);
    }
    let q = state.lastQuery;
    if (q instanceof RegExp && q.source === 'x^') {
      q = null;
    }
    if (!q) {
      return;
    }
    startSearch(cm, state, String(q));
    const tempCursor = cm.getCursor();
    state.posFrom = tempCursor;
    state.posTo = tempCursor;
    findNext(cm, rev);
  }
  function findNext(cm, rev) {
    cm.operation(function () {
      const state = getSearchState(cm);
      let cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
      if (rev) {
        // this makes a cursor jump off the current match to the previous match
        // not applicable for finding of the next match, because it will jump over
        cursor.find(rev);
      }
      if (!cursor.find(rev)) {
        cursor = getSearchCursor(cm, state.query, rev ? codeMirror.Pos(cm.lastLine()) : codeMirror.Pos(cm.firstLine(), 0));
        if (!cursor.find(rev)) {
          return;
        }
      }
      cm.setSelection(cursor.from(), cursor.to());
      cm.scrollIntoView({
        from: cursor.from(),
        to: cursor.to()
      }, 20);
      state.posFrom = cursor.from();
      state.posTo = cursor.to();
    });
  }
  function clearSearch(cm) {
    cm.operation(function () {
      const state = getSearchState(cm);
      state.lastQuery = state.query;
      if (!state.query) {
        return;
      }
      state.query = null;
      state.queryText = null;
      cm.removeOverlay(state.overlay);
      if (state.annotate) {
        state.annotate.clear();
        state.annotate = null;
      }
    });
  }
  return {
    doSearch,
    clearSearch
  };
}