import { findFirstInSorted } from "../../../../base/common/arrays.mjs";
import { TimeoutTimer, RunOnceScheduler } from "../../../../base/common/async.mjs";
import { DisposableStore, dispose } from "../../../../base/common/lifecycle.mjs";
import { ReplaceCommandThatPreservesSelection, ReplaceCommand } from "../../../common/commands/replaceCommand.mjs";
import { Position } from "../../../common/core/position.mjs";
import { Range } from "../../../common/core/range.mjs";
import { Selection } from "../../../common/core/selection.mjs";
import { SearchParams } from "../../../common/model/textModelSearch.mjs";
import { FindDecorations } from "./findDecorations.mjs";
import { ReplaceAllCommand } from "./replaceAllCommand.mjs";
import { parseReplaceString, ReplacePattern } from "./replacePattern.mjs";
import { RawContextKey } from "../../../../platform/contextkey/common/contextkey.mjs";
const CONTEXT_FIND_WIDGET_VISIBLE = new RawContextKey("findWidgetVisible", false);
CONTEXT_FIND_WIDGET_VISIBLE.toNegated();
const CONTEXT_FIND_INPUT_FOCUSED = new RawContextKey("findInputFocussed", false);
const CONTEXT_REPLACE_INPUT_FOCUSED = new RawContextKey("replaceInputFocussed", false);
const ToggleCaseSensitiveKeybinding = {
  primary: 512 | 33,
  mac: {
    primary: 2048 | 512 | 33
    /* KeyC */
  }
};
const ToggleWholeWordKeybinding = {
  primary: 512 | 53,
  mac: {
    primary: 2048 | 512 | 53
    /* KeyW */
  }
};
const ToggleRegexKeybinding = {
  primary: 512 | 48,
  mac: {
    primary: 2048 | 512 | 48
    /* KeyR */
  }
};
const ToggleSearchScopeKeybinding = {
  primary: 512 | 42,
  mac: {
    primary: 2048 | 512 | 42
    /* KeyL */
  }
};
const TogglePreserveCaseKeybinding = {
  primary: 512 | 46,
  mac: {
    primary: 2048 | 512 | 46
    /* KeyP */
  }
};
const FIND_IDS = {
  StartFindAction: "actions.find",
  StartFindWithSelection: "actions.findWithSelection",
  StartFindWithArgs: "editor.actions.findWithArgs",
  NextMatchFindAction: "editor.action.nextMatchFindAction",
  PreviousMatchFindAction: "editor.action.previousMatchFindAction",
  NextSelectionMatchFindAction: "editor.action.nextSelectionMatchFindAction",
  PreviousSelectionMatchFindAction: "editor.action.previousSelectionMatchFindAction",
  StartFindReplaceAction: "editor.action.startFindReplaceAction",
  CloseFindWidgetCommand: "closeFindWidget",
  ToggleCaseSensitiveCommand: "toggleFindCaseSensitive",
  ToggleWholeWordCommand: "toggleFindWholeWord",
  ToggleRegexCommand: "toggleFindRegex",
  ToggleSearchScopeCommand: "toggleFindInSelection",
  TogglePreserveCaseCommand: "togglePreserveCase",
  ReplaceOneAction: "editor.action.replaceOne",
  ReplaceAllAction: "editor.action.replaceAll",
  SelectAllMatchesAction: "editor.action.selectAllMatches"
};
const MATCHES_LIMIT = 19999;
const RESEARCH_DELAY = 240;
class FindModelBoundToEditorModel {
  constructor(editor, state) {
    this._toDispose = new DisposableStore();
    this._editor = editor;
    this._state = state;
    this._isDisposed = false;
    this._startSearchingTimer = new TimeoutTimer();
    this._decorations = new FindDecorations(editor);
    this._toDispose.add(this._decorations);
    this._updateDecorationsScheduler = new RunOnceScheduler(() => this.research(false), 100);
    this._toDispose.add(this._updateDecorationsScheduler);
    this._toDispose.add(this._editor.onDidChangeCursorPosition((e) => {
      if (e.reason === 3 || e.reason === 5 || e.reason === 6) {
        this._decorations.setStartPosition(this._editor.getPosition());
      }
    }));
    this._ignoreModelContentChanged = false;
    this._toDispose.add(this._editor.onDidChangeModelContent((e) => {
      if (this._ignoreModelContentChanged) {
        return;
      }
      if (e.isFlush) {
        this._decorations.reset();
      }
      this._decorations.setStartPosition(this._editor.getPosition());
      this._updateDecorationsScheduler.schedule();
    }));
    this._toDispose.add(this._state.onFindReplaceStateChange((e) => this._onStateChanged(e)));
    this.research(false, this._state.searchScope);
  }
  dispose() {
    this._isDisposed = true;
    dispose(this._startSearchingTimer);
    this._toDispose.dispose();
  }
  _onStateChanged(e) {
    if (this._isDisposed) {
      return;
    }
    if (!this._editor.hasModel()) {
      return;
    }
    if (e.searchString || e.isReplaceRevealed || e.isRegex || e.wholeWord || e.matchCase || e.searchScope) {
      let model = this._editor.getModel();
      if (model.isTooLargeForSyncing()) {
        this._startSearchingTimer.cancel();
        this._startSearchingTimer.setIfNotSet(() => {
          if (e.searchScope) {
            this.research(e.moveCursor, this._state.searchScope);
          } else {
            this.research(e.moveCursor);
          }
        }, RESEARCH_DELAY);
      } else {
        if (e.searchScope) {
          this.research(e.moveCursor, this._state.searchScope);
        } else {
          this.research(e.moveCursor);
        }
      }
    }
  }
  static _getSearchRange(model, findScope) {
    if (findScope) {
      return findScope;
    }
    return model.getFullModelRange();
  }
  research(moveCursor, newFindScope) {
    let findScopes = null;
    if (typeof newFindScope !== "undefined") {
      if (newFindScope !== null) {
        if (!Array.isArray(newFindScope)) {
          findScopes = [newFindScope];
        } else {
          findScopes = newFindScope;
        }
      }
    } else {
      findScopes = this._decorations.getFindScopes();
    }
    if (findScopes !== null) {
      findScopes = findScopes.map((findScope) => {
        if (findScope.startLineNumber !== findScope.endLineNumber) {
          let endLineNumber = findScope.endLineNumber;
          if (findScope.endColumn === 1) {
            endLineNumber = endLineNumber - 1;
          }
          return new Range(findScope.startLineNumber, 1, endLineNumber, this._editor.getModel().getLineMaxColumn(endLineNumber));
        }
        return findScope;
      });
    }
    let findMatches = this._findMatches(findScopes, false, MATCHES_LIMIT);
    this._decorations.set(findMatches, findScopes);
    const editorSelection = this._editor.getSelection();
    let currentMatchesPosition = this._decorations.getCurrentMatchesPosition(editorSelection);
    if (currentMatchesPosition === 0 && findMatches.length > 0) {
      const matchAfterSelection = findFirstInSorted(findMatches.map((match) => match.range), (range) => Range.compareRangesUsingStarts(range, editorSelection) >= 0);
      currentMatchesPosition = matchAfterSelection > 0 ? matchAfterSelection - 1 + 1 : currentMatchesPosition;
    }
    this._state.changeMatchInfo(currentMatchesPosition, this._decorations.getCount(), void 0);
    if (moveCursor && this._editor.getOption(
      35
      /* find */
    ).cursorMoveOnType) {
      this._moveToNextMatch(this._decorations.getStartPosition());
    }
  }
  _hasMatches() {
    return this._state.matchesCount > 0;
  }
  _cannotFind() {
    if (!this._hasMatches()) {
      let findScope = this._decorations.getFindScope();
      if (findScope) {
        this._editor.revealRangeInCenterIfOutsideViewport(
          findScope,
          0
          /* Smooth */
        );
      }
      return true;
    }
    return false;
  }
  _setCurrentFindMatch(match) {
    let matchesPosition = this._decorations.setCurrentFindMatch(match);
    this._state.changeMatchInfo(matchesPosition, this._decorations.getCount(), match);
    this._editor.setSelection(match);
    this._editor.revealRangeInCenterIfOutsideViewport(
      match,
      0
      /* Smooth */
    );
  }
  _prevSearchPosition(before) {
    let isUsingLineStops = this._state.isRegex && (this._state.searchString.indexOf("^") >= 0 || this._state.searchString.indexOf("$") >= 0);
    let { lineNumber, column } = before;
    let model = this._editor.getModel();
    if (isUsingLineStops || column === 1) {
      if (lineNumber === 1) {
        lineNumber = model.getLineCount();
      } else {
        lineNumber--;
      }
      column = model.getLineMaxColumn(lineNumber);
    } else {
      column--;
    }
    return new Position(lineNumber, column);
  }
  _moveToPrevMatch(before, isRecursed = false) {
    if (!this._state.canNavigateBack()) {
      const nextMatchRange = this._decorations.matchAfterPosition(before);
      if (nextMatchRange) {
        this._setCurrentFindMatch(nextMatchRange);
      }
      return;
    }
    if (this._decorations.getCount() < MATCHES_LIMIT) {
      let prevMatchRange = this._decorations.matchBeforePosition(before);
      if (prevMatchRange && prevMatchRange.isEmpty() && prevMatchRange.getStartPosition().equals(before)) {
        before = this._prevSearchPosition(before);
        prevMatchRange = this._decorations.matchBeforePosition(before);
      }
      if (prevMatchRange) {
        this._setCurrentFindMatch(prevMatchRange);
      }
      return;
    }
    if (this._cannotFind()) {
      return;
    }
    let findScope = this._decorations.getFindScope();
    let searchRange = FindModelBoundToEditorModel._getSearchRange(this._editor.getModel(), findScope);
    if (searchRange.getEndPosition().isBefore(before)) {
      before = searchRange.getEndPosition();
    }
    if (before.isBefore(searchRange.getStartPosition())) {
      before = searchRange.getEndPosition();
    }
    let { lineNumber, column } = before;
    let model = this._editor.getModel();
    let position = new Position(lineNumber, column);
    let prevMatch = model.findPreviousMatch(this._state.searchString, position, this._state.isRegex, this._state.matchCase, this._state.wholeWord ? this._editor.getOption(
      117
      /* wordSeparators */
    ) : null, false);
    if (prevMatch && prevMatch.range.isEmpty() && prevMatch.range.getStartPosition().equals(position)) {
      position = this._prevSearchPosition(position);
      prevMatch = model.findPreviousMatch(this._state.searchString, position, this._state.isRegex, this._state.matchCase, this._state.wholeWord ? this._editor.getOption(
        117
        /* wordSeparators */
      ) : null, false);
    }
    if (!prevMatch) {
      return;
    }
    if (!isRecursed && !searchRange.containsRange(prevMatch.range)) {
      return this._moveToPrevMatch(prevMatch.range.getStartPosition(), true);
    }
    this._setCurrentFindMatch(prevMatch.range);
  }
  moveToPrevMatch() {
    this._moveToPrevMatch(this._editor.getSelection().getStartPosition());
  }
  _nextSearchPosition(after) {
    let isUsingLineStops = this._state.isRegex && (this._state.searchString.indexOf("^") >= 0 || this._state.searchString.indexOf("$") >= 0);
    let { lineNumber, column } = after;
    let model = this._editor.getModel();
    if (isUsingLineStops || column === model.getLineMaxColumn(lineNumber)) {
      if (lineNumber === model.getLineCount()) {
        lineNumber = 1;
      } else {
        lineNumber++;
      }
      column = 1;
    } else {
      column++;
    }
    return new Position(lineNumber, column);
  }
  _moveToNextMatch(after) {
    if (!this._state.canNavigateForward()) {
      const prevMatchRange = this._decorations.matchBeforePosition(after);
      if (prevMatchRange) {
        this._setCurrentFindMatch(prevMatchRange);
      }
      return;
    }
    if (this._decorations.getCount() < MATCHES_LIMIT) {
      let nextMatchRange = this._decorations.matchAfterPosition(after);
      if (nextMatchRange && nextMatchRange.isEmpty() && nextMatchRange.getStartPosition().equals(after)) {
        after = this._nextSearchPosition(after);
        nextMatchRange = this._decorations.matchAfterPosition(after);
      }
      if (nextMatchRange) {
        this._setCurrentFindMatch(nextMatchRange);
      }
      return;
    }
    let nextMatch = this._getNextMatch(after, false, true);
    if (nextMatch) {
      this._setCurrentFindMatch(nextMatch.range);
    }
  }
  _getNextMatch(after, captureMatches, forceMove, isRecursed = false) {
    if (this._cannotFind()) {
      return null;
    }
    let findScope = this._decorations.getFindScope();
    let searchRange = FindModelBoundToEditorModel._getSearchRange(this._editor.getModel(), findScope);
    if (searchRange.getEndPosition().isBefore(after)) {
      after = searchRange.getStartPosition();
    }
    if (after.isBefore(searchRange.getStartPosition())) {
      after = searchRange.getStartPosition();
    }
    let { lineNumber, column } = after;
    let model = this._editor.getModel();
    let position = new Position(lineNumber, column);
    let nextMatch = model.findNextMatch(this._state.searchString, position, this._state.isRegex, this._state.matchCase, this._state.wholeWord ? this._editor.getOption(
      117
      /* wordSeparators */
    ) : null, captureMatches);
    if (forceMove && nextMatch && nextMatch.range.isEmpty() && nextMatch.range.getStartPosition().equals(position)) {
      position = this._nextSearchPosition(position);
      nextMatch = model.findNextMatch(this._state.searchString, position, this._state.isRegex, this._state.matchCase, this._state.wholeWord ? this._editor.getOption(
        117
        /* wordSeparators */
      ) : null, captureMatches);
    }
    if (!nextMatch) {
      return null;
    }
    if (!isRecursed && !searchRange.containsRange(nextMatch.range)) {
      return this._getNextMatch(nextMatch.range.getEndPosition(), captureMatches, forceMove, true);
    }
    return nextMatch;
  }
  moveToNextMatch() {
    this._moveToNextMatch(this._editor.getSelection().getEndPosition());
  }
  _getReplacePattern() {
    if (this._state.isRegex) {
      return parseReplaceString(this._state.replaceString);
    }
    return ReplacePattern.fromStaticValue(this._state.replaceString);
  }
  replace() {
    if (!this._hasMatches()) {
      return;
    }
    let replacePattern = this._getReplacePattern();
    let selection = this._editor.getSelection();
    let nextMatch = this._getNextMatch(selection.getStartPosition(), true, false);
    if (nextMatch) {
      if (selection.equalsRange(nextMatch.range)) {
        let replaceString = replacePattern.buildReplaceString(nextMatch.matches, this._state.preserveCase);
        let command = new ReplaceCommand(selection, replaceString);
        this._executeEditorCommand("replace", command);
        this._decorations.setStartPosition(new Position(selection.startLineNumber, selection.startColumn + replaceString.length));
        this.research(true);
      } else {
        this._decorations.setStartPosition(this._editor.getPosition());
        this._setCurrentFindMatch(nextMatch.range);
      }
    }
  }
  _findMatches(findScopes, captureMatches, limitResultCount) {
    const searchRanges = (findScopes || [null]).map((scope) => FindModelBoundToEditorModel._getSearchRange(this._editor.getModel(), scope));
    return this._editor.getModel().findMatches(this._state.searchString, searchRanges, this._state.isRegex, this._state.matchCase, this._state.wholeWord ? this._editor.getOption(
      117
      /* wordSeparators */
    ) : null, captureMatches, limitResultCount);
  }
  replaceAll() {
    if (!this._hasMatches()) {
      return;
    }
    const findScopes = this._decorations.getFindScopes();
    if (findScopes === null && this._state.matchesCount >= MATCHES_LIMIT) {
      this._largeReplaceAll();
    } else {
      this._regularReplaceAll(findScopes);
    }
    this.research(false);
  }
  _largeReplaceAll() {
    const searchParams = new SearchParams(this._state.searchString, this._state.isRegex, this._state.matchCase, this._state.wholeWord ? this._editor.getOption(
      117
      /* wordSeparators */
    ) : null);
    const searchData = searchParams.parseSearchRequest();
    if (!searchData) {
      return;
    }
    let searchRegex = searchData.regex;
    if (!searchRegex.multiline) {
      let mod = "mu";
      if (searchRegex.ignoreCase) {
        mod += "i";
      }
      if (searchRegex.global) {
        mod += "g";
      }
      searchRegex = new RegExp(searchRegex.source, mod);
    }
    const model = this._editor.getModel();
    const modelText = model.getValue(
      1
      /* LF */
    );
    const fullModelRange = model.getFullModelRange();
    const replacePattern = this._getReplacePattern();
    let resultText;
    const preserveCase = this._state.preserveCase;
    if (replacePattern.hasReplacementPatterns || preserveCase) {
      resultText = modelText.replace(searchRegex, function() {
        return replacePattern.buildReplaceString(arguments, preserveCase);
      });
    } else {
      resultText = modelText.replace(searchRegex, replacePattern.buildReplaceString(null, preserveCase));
    }
    let command = new ReplaceCommandThatPreservesSelection(fullModelRange, resultText, this._editor.getSelection());
    this._executeEditorCommand("replaceAll", command);
  }
  _regularReplaceAll(findScopes) {
    const replacePattern = this._getReplacePattern();
    let matches = this._findMatches(
      findScopes,
      replacePattern.hasReplacementPatterns || this._state.preserveCase,
      1073741824
      /* MAX_SAFE_SMALL_INTEGER */
    );
    let replaceStrings = [];
    for (let i = 0, len = matches.length; i < len; i++) {
      replaceStrings[i] = replacePattern.buildReplaceString(matches[i].matches, this._state.preserveCase);
    }
    let command = new ReplaceAllCommand(this._editor.getSelection(), matches.map((m) => m.range), replaceStrings);
    this._executeEditorCommand("replaceAll", command);
  }
  selectAllMatches() {
    if (!this._hasMatches()) {
      return;
    }
    let findScopes = this._decorations.getFindScopes();
    let matches = this._findMatches(
      findScopes,
      false,
      1073741824
      /* MAX_SAFE_SMALL_INTEGER */
    );
    let selections = matches.map((m) => new Selection(m.range.startLineNumber, m.range.startColumn, m.range.endLineNumber, m.range.endColumn));
    let editorSelection = this._editor.getSelection();
    for (let i = 0, len = selections.length; i < len; i++) {
      let sel = selections[i];
      if (sel.equalsRange(editorSelection)) {
        selections = [editorSelection].concat(selections.slice(0, i)).concat(selections.slice(i + 1));
        break;
      }
    }
    this._editor.setSelections(selections);
  }
  _executeEditorCommand(source, command) {
    try {
      this._ignoreModelContentChanged = true;
      this._editor.pushUndoStop();
      this._editor.executeCommand(source, command);
      this._editor.pushUndoStop();
    } finally {
      this._ignoreModelContentChanged = false;
    }
  }
}
export {
  CONTEXT_FIND_INPUT_FOCUSED,
  CONTEXT_FIND_WIDGET_VISIBLE,
  CONTEXT_REPLACE_INPUT_FOCUSED,
  FIND_IDS,
  FindModelBoundToEditorModel,
  MATCHES_LIMIT,
  ToggleCaseSensitiveKeybinding,
  TogglePreserveCaseKeybinding,
  ToggleRegexKeybinding,
  ToggleSearchScopeKeybinding,
  ToggleWholeWordKeybinding
};
