text: "use strict";
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
      if (k2 === undefined) k2 = k;
      Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
  }) : (function(o, m, k, k2) {
      if (k2 === undefined) k2 = k;
      o[k2] = m[k];
  }));
  var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
      Object.defineProperty(o, "default", { enumerable: true, value: v });
  }) : function(o, v) {
      o["default"] = v;
  });
  var __importStar = (this && this.__importStar) || function (mod) {
      if (mod && mod.__esModule) return mod;
      var result = {};
      if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
      __setModuleDefault(result, mod);
      return result;
  };
  Object.defineProperty(exports, "__esModule", { value: true });
  exports._testMethods = exports.hasWordCheck = exports.isWordValid = exports.calcTextInclusionRanges = exports.validateText = exports.minWordSplitLen = exports.defaultMinWordLength = exports.defaultMaxDuplicateProblems = exports.defaultMaxNumberOfProblems = void 0;
  const RxPat = __importStar(require("./Settings/RegExpPatterns"));
  const Text = __importStar(require("./util/text"));
  const TextRange = __importStar(require("./util/TextRange"));
  const wordSplitter_1 = require("./util/wordSplitter");
  exports.defaultMaxNumberOfProblems = 200;
  exports.defaultMaxDuplicateProblems = 5;
  exports.defaultMinWordLength = 4;
  exports.minWordSplitLen = 3;
  function validateText(text, dict, options) {
      const { maxNumberOfProblems = exports.defaultMaxNumberOfProblems, maxDuplicateProblems = exports.defaultMaxDuplicateProblems } = options;
      const mapOfProblems = new Map();
      const includeRanges = calcTextInclusionRanges(text, options);
      const validator = lineValidator(dict, options);
      return Text.extractLinesOfText(text)
          .concatMap(mapTextOffsetsAgainstRanges(includeRanges))
          .concatMap(validator)
          .filter((wo) => {
          const word = wo.text;
          // Keep track of the number of times we have seen the same problem
          const n = (mapOfProblems.get(word) || 0) + 1;
          mapOfProblems.set(word, n);
          // Filter out if there is too many
          return n < maxDuplicateProblems;
      })
          .take(maxNumberOfProblems);
  }
  exports.validateText = validateText;
  function calcTextInclusionRanges(text, options) {
      const { ignoreRegExpList = [], includeRegExpList = [] } = options;
      const filteredIncludeList = includeRegExpList.filter((a) => !!a);
      const finalIncludeList = filteredIncludeList.length ? filteredIncludeList : ['.*'];
      const includeRanges = TextRange.excludeRanges(TextRange.findMatchingRangesForPatterns(finalIncludeList, text), TextRange.findMatchingRangesForPatterns(ignoreRegExpList, text));
      return includeRanges;
  }
  exports.calcTextInclusionRanges = calcTextInclusionRanges;
  function lineValidator(dict, options) {
      const { minWordLength = exports.defaultMinWordLength, flagWords = [], allowCompoundWords = false, ignoreCase = true, } = options;
      const hasWordOptions = {
          ignoreCase,
          useCompounds: allowCompoundWords || undefined, // let the dictionaries decide on useCompounds if allow is false
      };
      const dictCol = dict;
      const setOfFlagWords = new Set(flagWords);
      const setOfKnownSuccessfulWords = new Set();
      const rememberFilter = (fn) => (v) => {
          const keep = fn(v);
          if (!keep) {
              setOfKnownSuccessfulWords.add(v.text);
          }
          return keep;
      };
      const filterAlreadyChecked = (wo) => {
          return !setOfKnownSuccessfulWords.has(wo.text);
      };
      function testForFlaggedWord(wo) {
          const text = wo.text;
          return setOfFlagWords.has(text) || setOfFlagWords.has(text.toLowerCase()) || dictCol.isForbidden(text);
      }
      function isWordIgnored(word) {
          return dict.isNoSuggestWord(word, options);
      }
      function checkFlagWords(word) {
          const isIgnored = isWordIgnored(word.text);
          const isFlagged = !isIgnored && testForFlaggedWord(word);
          word.isFlagged = isFlagged;
          return word;
      }
      function checkWord(word, options) {
          const isIgnored = isWordIgnored(word.text);
          const { isFlagged = !isIgnored && testForFlaggedWord(word) } = word;
          const isFound = isFlagged ? undefined : isIgnored || isWordValid(dictCol, word, word.line, options);
          return { ...word, isFlagged, isFound };
      }
      const fn = (lineSegment) => {
          function splitterIsValid(word) {
              return (setOfKnownSuccessfulWords.has(word.text) ||
                  (!testForFlaggedWord(word) && isWordValid(dictCol, word, lineSegment, hasWordOptions)));
          }
          function checkFullWord(vr) {
              if (vr.isFlagged) {
                  return [vr];
              }
              const codeWordResults = Text.extractWordsFromCodeTextOffset(vr)
                  .filter(filterAlreadyChecked)
                  .filter(rememberFilter((wo) => wo.text.length >= minWordLength))
                  .map((t) => ({ ...t, line: vr.line }))
                  .map((wo) => {
                  const vr = wo;
                  return vr;
              })
                  .map((wo) => (wo.isFlagged ? wo : checkWord(wo, hasWordOptions)))
                  .filter(rememberFilter((wo) => wo.isFlagged || !wo.isFound))
                  .filter(rememberFilter((wo) => !RxPat.regExRepeatedChar.test(wo.text))) // Filter out any repeated characters like xxxxxxxxxx
                  // get back the original text.
                  .map((wo) => ({
                  ...wo,
                  text: Text.extractText(lineSegment, wo.offset, wo.offset + wo.text.length),
              }))
                  .toArray();
              if (!codeWordResults.length || isWordIgnored(vr.text) || checkWord(vr, hasWordOptions).isFound) {
                  rememberFilter((_) => false)(vr);
                  return [];
              }
              return codeWordResults;
          }
          function checkPossibleWords(possibleWord) {
              const mismatches = Text.extractWordsFromTextOffset(possibleWord)
                  .filter(filterAlreadyChecked)
                  .filter(rememberFilter((wo) => wo.text.length >= minWordLength))
                  .map((wo) => ({ ...wo, line: lineSegment }))
                  .map(checkFlagWords)
                  .concatMap(checkFullWord)
                  .toArray();
              if (mismatches.length) {
                  // Try the more expensive word splitter
                  const splitResult = (0, wordSplitter_1.split)(lineSegment, possibleWord.offset, splitterIsValid);
                  const nonMatching = splitResult.words.filter((w) => !w.isFound);
                  if (nonMatching.length < mismatches.length) {
                      return nonMatching.map((w) => ({ ...w, line: lineSegment })).map(checkFlagWords);
                  }
              }
              return mismatches;
          }
          const checkedPossibleWords = Text.extractPossibleWordsFromTextOffset(lineSegment)
              .filter(filterAlreadyChecked)
              .concatMap(checkPossibleWords);
          return checkedPossibleWords;
      };
      return fn;
  }
  function isWordValid(dict, wo, line, options) {
      const firstTry = hasWordCheck(dict, wo.text, options);
      return (firstTry ||
          // Drop the first letter if it is preceded by a '\'.
          (line.text[wo.offset - line.offset - 1] === '\\' && hasWordCheck(dict, wo.text.slice(1), options)));
  }
  exports.isWordValid = isWordValid;
  function hasWordCheck(dict, word, options) {
      word = word.replace(/\\/g, '');
      // Do not pass allowCompounds down if it is false, that allows for the dictionary to override the value based upon its own settings.
      return dict.has(word, convertCheckOptionsToHasOptions(options));
  }
  exports.hasWordCheck = hasWordCheck;
  function convertCheckOptionsToHasOptions(opt) {
      const { ignoreCase, useCompounds } = opt;
      return {
          ignoreCase,
          useCompounds,
      };
  }
  /**
   * Returns a mapper function that will
   * @param includeRanges Allowed ranges for words.
   */
  function mapTextOffsetsAgainstRanges(includeRanges) {
      let rangePos = 0;
      const mapper = (textOffset) => {
          if (!includeRanges.length) {
              return [];
          }
          const parts = [];
          const { text, offset } = textOffset;
          const wordEndPos = offset + text.length;
          let wordStartPos = offset;
          while (rangePos && (rangePos >= includeRanges.length || includeRanges[rangePos].startPos > wordStartPos)) {
              rangePos -= 1;
          }
          const cur = includeRanges[rangePos];
          if (wordEndPos <= cur.endPos && wordStartPos >= cur.startPos) {
              return [textOffset];
          }
          while (wordStartPos < wordEndPos) {
              while (includeRanges[rangePos] && includeRanges[rangePos].endPos <= wordStartPos) {
                  rangePos += 1;
              }
              if (!includeRanges[rangePos]) {
                  break;
              }
              const { startPos, endPos } = includeRanges[rangePos];
              if (wordEndPos < startPos) {
                  break;
              }
              const a = Math.max(wordStartPos, startPos);
              const b = Math.min(wordEndPos, endPos);
              if (a !== b) {
                  parts.push({ offset: a, text: text.slice(a - offset, b - offset) });
              }
              wordStartPos = b;
          }
          return parts;
      };
      return mapper;
  }
  exports._testMethods = {
      mapWordsAgainstRanges: mapTextOffsetsAgainstRanges,
  };
  //# sourceMappingURL=textValidator.js.map