"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const arrays = require("../../../../base/common/arrays.js");
const lifecycle = require("../../../../base/common/lifecycle.js");
const strings = require("../../../../base/common/strings.js");
require("./snippetSession.css.js");
const editOperation = require("../../../common/core/editOperation.js");
const range = require("../../../common/core/range.js");
const selection = require("../../../common/core/selection.js");
const textModel = require("../../../common/model/textModel.js");
const label = require("../../../../platform/label/common/label.js");
const workspace = require("../../../../platform/workspace/common/workspace.js");
const snippetParser = require("./snippetParser.js");
const snippetVariables = require("./snippetVariables.js");
class OneSnippet {
  constructor(_editor, _snippet, _offset, _snippetLineLeadingWhitespace) {
    this._editor = _editor;
    this._snippet = _snippet;
    this._offset = _offset;
    this._snippetLineLeadingWhitespace = _snippetLineLeadingWhitespace;
    this._nestingLevel = 1;
    this._placeholderGroups = arrays.groupBy(_snippet.placeholders, snippetParser.Placeholder.compareByIndex);
    this._placeholderGroupsIdx = -1;
  }
  dispose() {
    if (this._placeholderDecorations) {
      this._editor.deltaDecorations([...this._placeholderDecorations.values()], []);
    }
    this._placeholderGroups.length = 0;
  }
  _initDecorations() {
    if (this._placeholderDecorations) {
      return;
    }
    this._placeholderDecorations = /* @__PURE__ */ new Map();
    const model = this._editor.getModel();
    this._editor.changeDecorations((accessor) => {
      for (const placeholder of this._snippet.placeholders) {
        const placeholderOffset = this._snippet.offset(placeholder);
        const placeholderLen = this._snippet.fullLen(placeholder);
        const range$1 = range.Range.fromPositions(model.getPositionAt(this._offset + placeholderOffset), model.getPositionAt(this._offset + placeholderOffset + placeholderLen));
        const options = placeholder.isFinalTabstop ? OneSnippet._decor.inactiveFinal : OneSnippet._decor.inactive;
        const handle = accessor.addDecoration(range$1, options);
        this._placeholderDecorations.set(placeholder, handle);
      }
    });
  }
  move(fwd) {
    if (!this._editor.hasModel()) {
      return [];
    }
    this._initDecorations();
    if (this._placeholderGroupsIdx >= 0) {
      let operations = [];
      for (const placeholder of this._placeholderGroups[this._placeholderGroupsIdx]) {
        if (placeholder.transform) {
          const id = this._placeholderDecorations.get(placeholder);
          const range2 = this._editor.getModel().getDecorationRange(id);
          const currentValue = this._editor.getModel().getValueInRange(range2);
          const transformedValueLines = placeholder.transform.resolve(currentValue).split(/\r\n|\r|\n/);
          for (let i = 1; i < transformedValueLines.length; i++) {
            transformedValueLines[i] = this._editor.getModel().normalizeIndentation(this._snippetLineLeadingWhitespace + transformedValueLines[i]);
          }
          operations.push(editOperation.EditOperation.replace(range2, transformedValueLines.join(this._editor.getModel().getEOL())));
        }
      }
      if (operations.length > 0) {
        this._editor.executeEdits("snippet.placeholderTransform", operations);
      }
    }
    let couldSkipThisPlaceholder = false;
    if (fwd === true && this._placeholderGroupsIdx < this._placeholderGroups.length - 1) {
      this._placeholderGroupsIdx += 1;
      couldSkipThisPlaceholder = true;
    } else if (fwd === false && this._placeholderGroupsIdx > 0) {
      this._placeholderGroupsIdx -= 1;
      couldSkipThisPlaceholder = true;
    } else
      ;
    const newSelections = this._editor.getModel().changeDecorations((accessor) => {
      const activePlaceholders = /* @__PURE__ */ new Set();
      const selections = [];
      for (const placeholder of this._placeholderGroups[this._placeholderGroupsIdx]) {
        const id = this._placeholderDecorations.get(placeholder);
        const range2 = this._editor.getModel().getDecorationRange(id);
        selections.push(new selection.Selection(range2.startLineNumber, range2.startColumn, range2.endLineNumber, range2.endColumn));
        couldSkipThisPlaceholder = couldSkipThisPlaceholder && this._hasPlaceholderBeenCollapsed(placeholder);
        accessor.changeDecorationOptions(id, placeholder.isFinalTabstop ? OneSnippet._decor.activeFinal : OneSnippet._decor.active);
        activePlaceholders.add(placeholder);
        for (const enclosingPlaceholder of this._snippet.enclosingPlaceholders(placeholder)) {
          const id2 = this._placeholderDecorations.get(enclosingPlaceholder);
          accessor.changeDecorationOptions(id2, enclosingPlaceholder.isFinalTabstop ? OneSnippet._decor.activeFinal : OneSnippet._decor.active);
          activePlaceholders.add(enclosingPlaceholder);
        }
      }
      for (const [placeholder, id] of this._placeholderDecorations) {
        if (!activePlaceholders.has(placeholder)) {
          accessor.changeDecorationOptions(id, placeholder.isFinalTabstop ? OneSnippet._decor.inactiveFinal : OneSnippet._decor.inactive);
        }
      }
      return selections;
    });
    return !couldSkipThisPlaceholder ? newSelections !== null && newSelections !== void 0 ? newSelections : [] : this.move(fwd);
  }
  _hasPlaceholderBeenCollapsed(placeholder) {
    let marker = placeholder;
    while (marker) {
      if (marker instanceof snippetParser.Placeholder) {
        const id = this._placeholderDecorations.get(marker);
        const range2 = this._editor.getModel().getDecorationRange(id);
        if (range2.isEmpty() && marker.toString().length > 0) {
          return true;
        }
      }
      marker = marker.parent;
    }
    return false;
  }
  get isAtFirstPlaceholder() {
    return this._placeholderGroupsIdx <= 0 || this._placeholderGroups.length === 0;
  }
  get isAtLastPlaceholder() {
    return this._placeholderGroupsIdx === this._placeholderGroups.length - 1;
  }
  get hasPlaceholder() {
    return this._snippet.placeholders.length > 0;
  }
  computePossibleSelections() {
    const result = /* @__PURE__ */ new Map();
    for (const placeholdersWithEqualIndex of this._placeholderGroups) {
      let ranges;
      for (const placeholder of placeholdersWithEqualIndex) {
        if (placeholder.isFinalTabstop) {
          break;
        }
        if (!ranges) {
          ranges = [];
          result.set(placeholder.index, ranges);
        }
        const id = this._placeholderDecorations.get(placeholder);
        const range2 = this._editor.getModel().getDecorationRange(id);
        if (!range2) {
          result.delete(placeholder.index);
          break;
        }
        ranges.push(range2);
      }
    }
    return result;
  }
  get choice() {
    return this._placeholderGroups[this._placeholderGroupsIdx][0].choice;
  }
  merge(others) {
    const model = this._editor.getModel();
    this._nestingLevel *= 10;
    this._editor.changeDecorations((accessor) => {
      for (const placeholder of this._placeholderGroups[this._placeholderGroupsIdx]) {
        const nested = others.shift();
        console.assert(!nested._placeholderDecorations);
        const indexLastPlaceholder = nested._snippet.placeholderInfo.last.index;
        for (const nestedPlaceholder of nested._snippet.placeholderInfo.all) {
          if (nestedPlaceholder.isFinalTabstop) {
            nestedPlaceholder.index = placeholder.index + (indexLastPlaceholder + 1) / this._nestingLevel;
          } else {
            nestedPlaceholder.index = placeholder.index + nestedPlaceholder.index / this._nestingLevel;
          }
        }
        this._snippet.replace(placeholder, nested._snippet.children);
        const id = this._placeholderDecorations.get(placeholder);
        accessor.removeDecoration(id);
        this._placeholderDecorations.delete(placeholder);
        for (const placeholder2 of nested._snippet.placeholders) {
          const placeholderOffset = nested._snippet.offset(placeholder2);
          const placeholderLen = nested._snippet.fullLen(placeholder2);
          const range$1 = range.Range.fromPositions(model.getPositionAt(nested._offset + placeholderOffset), model.getPositionAt(nested._offset + placeholderOffset + placeholderLen));
          const handle = accessor.addDecoration(range$1, OneSnippet._decor.inactive);
          this._placeholderDecorations.set(placeholder2, handle);
        }
      }
      this._placeholderGroups = arrays.groupBy(this._snippet.placeholders, snippetParser.Placeholder.compareByIndex);
    });
  }
}
OneSnippet._decor = {
  active: textModel.ModelDecorationOptions.register({ description: "snippet-placeholder-1", stickiness: 0, className: "snippet-placeholder" }),
  inactive: textModel.ModelDecorationOptions.register({ description: "snippet-placeholder-2", stickiness: 1, className: "snippet-placeholder" }),
  activeFinal: textModel.ModelDecorationOptions.register({ description: "snippet-placeholder-3", stickiness: 1, className: "finish-snippet-placeholder" }),
  inactiveFinal: textModel.ModelDecorationOptions.register({ description: "snippet-placeholder-4", stickiness: 1, className: "finish-snippet-placeholder" })
};
const _defaultOptions = {
  overwriteBefore: 0,
  overwriteAfter: 0,
  adjustWhitespace: true,
  clipboardText: void 0,
  overtypingCapturer: void 0
};
class SnippetSession {
  constructor(editor, template, options = _defaultOptions) {
    this._templateMerges = [];
    this._snippets = [];
    this._editor = editor;
    this._template = template;
    this._options = options;
  }
  static adjustWhitespace(model, position, snippet, adjustIndentation, adjustNewlines) {
    const line = model.getLineContent(position.lineNumber);
    const lineLeadingWhitespace = strings.getLeadingWhitespace(line, 0, position.column - 1);
    let snippetTextString;
    snippet.walk((marker) => {
      if (!(marker instanceof snippetParser.Text) || marker.parent instanceof snippetParser.Choice) {
        return true;
      }
      const lines = marker.value.split(/\r\n|\r|\n/);
      if (adjustIndentation) {
        const offset = snippet.offset(marker);
        if (offset === 0) {
          lines[0] = model.normalizeIndentation(lines[0]);
        } else {
          snippetTextString = snippetTextString !== null && snippetTextString !== void 0 ? snippetTextString : snippet.toString();
          let prevChar = snippetTextString.charCodeAt(offset - 1);
          if (prevChar === 10 || prevChar === 13) {
            lines[0] = model.normalizeIndentation(lineLeadingWhitespace + lines[0]);
          }
        }
        for (let i = 1; i < lines.length; i++) {
          lines[i] = model.normalizeIndentation(lineLeadingWhitespace + lines[i]);
        }
      }
      const newValue = lines.join(model.getEOL());
      if (newValue !== marker.value) {
        marker.parent.replace(marker, [new snippetParser.Text(newValue)]);
        snippetTextString = void 0;
      }
      return true;
    });
    return lineLeadingWhitespace;
  }
  static adjustSelection(model, selection$1, overwriteBefore, overwriteAfter) {
    if (overwriteBefore !== 0 || overwriteAfter !== 0) {
      const { positionLineNumber, positionColumn } = selection$1;
      const positionColumnBefore = positionColumn - overwriteBefore;
      const positionColumnAfter = positionColumn + overwriteAfter;
      const range2 = model.validateRange({
        startLineNumber: positionLineNumber,
        startColumn: positionColumnBefore,
        endLineNumber: positionLineNumber,
        endColumn: positionColumnAfter
      });
      selection$1 = selection.Selection.createWithDirection(range2.startLineNumber, range2.startColumn, range2.endLineNumber, range2.endColumn, selection$1.getDirection());
    }
    return selection$1;
  }
  static createEditsAndSnippets(editor, template, overwriteBefore, overwriteAfter, enforceFinalTabstop, adjustWhitespace, clipboardText, overtypingCapturer) {
    const edits = [];
    const snippets = [];
    if (!editor.hasModel()) {
      return { edits, snippets };
    }
    const model = editor.getModel();
    const workspaceService = editor.invokeWithinContext((accessor) => accessor.get(workspace.IWorkspaceContextService));
    const modelBasedVariableResolver = editor.invokeWithinContext((accessor) => new snippetVariables.ModelBasedVariableResolver(accessor.get(label.ILabelService), model));
    const readClipboardText = () => clipboardText;
    let delta = 0;
    let firstBeforeText = model.getValueInRange(SnippetSession.adjustSelection(model, editor.getSelection(), overwriteBefore, 0));
    let firstAfterText = model.getValueInRange(SnippetSession.adjustSelection(model, editor.getSelection(), 0, overwriteAfter));
    let firstLineFirstNonWhitespace = model.getLineFirstNonWhitespaceColumn(editor.getSelection().positionLineNumber);
    const indexedSelections = editor.getSelections().map((selection2, idx) => ({ selection: selection2, idx })).sort((a, b) => range.Range.compareRangesUsingStarts(a.selection, b.selection));
    for (const { selection: selection2, idx } of indexedSelections) {
      let extensionBefore = SnippetSession.adjustSelection(model, selection2, overwriteBefore, 0);
      let extensionAfter = SnippetSession.adjustSelection(model, selection2, 0, overwriteAfter);
      if (firstBeforeText !== model.getValueInRange(extensionBefore)) {
        extensionBefore = selection2;
      }
      if (firstAfterText !== model.getValueInRange(extensionAfter)) {
        extensionAfter = selection2;
      }
      const snippetSelection = selection2.setStartPosition(extensionBefore.startLineNumber, extensionBefore.startColumn).setEndPosition(extensionAfter.endLineNumber, extensionAfter.endColumn);
      const snippet = new snippetParser.SnippetParser().parse(template, true, enforceFinalTabstop);
      const start = snippetSelection.getStartPosition();
      const snippetLineLeadingWhitespace = SnippetSession.adjustWhitespace(model, start, snippet, adjustWhitespace || idx > 0 && firstLineFirstNonWhitespace !== model.getLineFirstNonWhitespaceColumn(selection2.positionLineNumber), true);
      snippet.resolveVariables(new snippetVariables.CompositeSnippetVariableResolver([
        modelBasedVariableResolver,
        new snippetVariables.ClipboardBasedVariableResolver(readClipboardText, idx, indexedSelections.length, editor.getOption(
          71
          /* multiCursorPaste */
        ) === "spread"),
        new snippetVariables.SelectionBasedVariableResolver(model, selection2, idx, overtypingCapturer),
        new snippetVariables.CommentBasedVariableResolver(model, selection2),
        new snippetVariables.TimeBasedVariableResolver(),
        new snippetVariables.WorkspaceBasedVariableResolver(workspaceService),
        new snippetVariables.RandomBasedVariableResolver()
      ]));
      const offset = model.getOffsetAt(start) + delta;
      delta += snippet.toString().length - model.getValueLengthInRange(snippetSelection);
      edits[idx] = editOperation.EditOperation.replace(snippetSelection, snippet.toString());
      edits[idx].identifier = { major: idx, minor: 0 };
      snippets[idx] = new OneSnippet(editor, snippet, offset, snippetLineLeadingWhitespace);
    }
    return { edits, snippets };
  }
  dispose() {
    lifecycle.dispose(this._snippets);
  }
  _logInfo() {
    return `template="${this._template}", merged_templates="${this._templateMerges.join(" -> ")}"`;
  }
  insert() {
    if (!this._editor.hasModel()) {
      return;
    }
    const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, this._template, this._options.overwriteBefore, this._options.overwriteAfter, false, this._options.adjustWhitespace, this._options.clipboardText, this._options.overtypingCapturer);
    this._snippets = snippets;
    this._editor.executeEdits("snippet", edits, (undoEdits) => {
      if (this._snippets[0].hasPlaceholder) {
        return this._move(true);
      } else {
        return undoEdits.filter((edit) => !!edit.identifier).map((edit) => selection.Selection.fromPositions(edit.range.getEndPosition()));
      }
    });
    this._editor.revealRange(this._editor.getSelections()[0]);
  }
  merge(template, options = _defaultOptions) {
    if (!this._editor.hasModel()) {
      return;
    }
    this._templateMerges.push([this._snippets[0]._nestingLevel, this._snippets[0]._placeholderGroupsIdx, template]);
    const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, template, options.overwriteBefore, options.overwriteAfter, true, options.adjustWhitespace, options.clipboardText, options.overtypingCapturer);
    this._editor.executeEdits("snippet", edits, (undoEdits) => {
      for (const snippet of this._snippets) {
        snippet.merge(snippets);
      }
      console.assert(snippets.length === 0);
      if (this._snippets[0].hasPlaceholder) {
        return this._move(void 0);
      } else {
        return undoEdits.filter((edit) => !!edit.identifier).map((edit) => selection.Selection.fromPositions(edit.range.getEndPosition()));
      }
    });
  }
  next() {
    const newSelections = this._move(true);
    this._editor.setSelections(newSelections);
    this._editor.revealPositionInCenterIfOutsideViewport(newSelections[0].getPosition());
  }
  prev() {
    const newSelections = this._move(false);
    this._editor.setSelections(newSelections);
    this._editor.revealPositionInCenterIfOutsideViewport(newSelections[0].getPosition());
  }
  _move(fwd) {
    const selections = [];
    for (const snippet of this._snippets) {
      const oneSelection = snippet.move(fwd);
      selections.push(...oneSelection);
    }
    return selections;
  }
  get isAtFirstPlaceholder() {
    return this._snippets[0].isAtFirstPlaceholder;
  }
  get isAtLastPlaceholder() {
    return this._snippets[0].isAtLastPlaceholder;
  }
  get hasPlaceholder() {
    return this._snippets[0].hasPlaceholder;
  }
  get choice() {
    return this._snippets[0].choice;
  }
  isSelectionWithinPlaceholders() {
    if (!this.hasPlaceholder) {
      return false;
    }
    const selections = this._editor.getSelections();
    if (selections.length < this._snippets.length) {
      return false;
    }
    let allPossibleSelections = /* @__PURE__ */ new Map();
    for (const snippet of this._snippets) {
      const possibleSelections = snippet.computePossibleSelections();
      if (allPossibleSelections.size === 0) {
        for (const [index, ranges] of possibleSelections) {
          ranges.sort(range.Range.compareRangesUsingStarts);
          for (const selection2 of selections) {
            if (ranges[0].containsRange(selection2)) {
              allPossibleSelections.set(index, []);
              break;
            }
          }
        }
      }
      if (allPossibleSelections.size === 0) {
        return false;
      }
      allPossibleSelections.forEach((array, index) => {
        array.push(...possibleSelections.get(index));
      });
    }
    selections.sort(range.Range.compareRangesUsingStarts);
    for (let [index, ranges] of allPossibleSelections) {
      if (ranges.length !== selections.length) {
        allPossibleSelections.delete(index);
        continue;
      }
      ranges.sort(range.Range.compareRangesUsingStarts);
      for (let i = 0; i < ranges.length; i++) {
        if (!ranges[i].containsRange(selections[i])) {
          allPossibleSelections.delete(index);
          continue;
        }
      }
    }
    return allPossibleSelections.size > 0;
  }
}
exports.OneSnippet = OneSnippet;
exports.SnippetSession = SnippetSession;
