import { writeUInt32BE, writeUInt16LE, readUInt32BE } from "../../../base/common/buffer.mjs";
import { decodeUTF16LE } from "./stringBuilder.mjs";
function escapeNewLine(str) {
  return str.replace(/\n/g, "\\n").replace(/\r/g, "\\r");
}
class TextChange {
  constructor(oldPosition, oldText, newPosition, newText) {
    this.oldPosition = oldPosition;
    this.oldText = oldText;
    this.newPosition = newPosition;
    this.newText = newText;
  }
  get oldLength() {
    return this.oldText.length;
  }
  get oldEnd() {
    return this.oldPosition + this.oldText.length;
  }
  get newLength() {
    return this.newText.length;
  }
  get newEnd() {
    return this.newPosition + this.newText.length;
  }
  toString() {
    if (this.oldText.length === 0) {
      return `(insert@${this.oldPosition} "${escapeNewLine(this.newText)}")`;
    }
    if (this.newText.length === 0) {
      return `(delete@${this.oldPosition} "${escapeNewLine(this.oldText)}")`;
    }
    return `(replace@${this.oldPosition} "${escapeNewLine(this.oldText)}" with "${escapeNewLine(this.newText)}")`;
  }
  static _writeStringSize(str) {
    return 4 + 2 * str.length;
  }
  static _writeString(b, str, offset) {
    const len = str.length;
    writeUInt32BE(b, len, offset);
    offset += 4;
    for (let i = 0; i < len; i++) {
      writeUInt16LE(b, str.charCodeAt(i), offset);
      offset += 2;
    }
    return offset;
  }
  static _readString(b, offset) {
    const len = readUInt32BE(b, offset);
    offset += 4;
    return decodeUTF16LE(b, offset, len);
  }
  writeSize() {
    return 4 + 4 + TextChange._writeStringSize(this.oldText) + TextChange._writeStringSize(this.newText);
  }
  write(b, offset) {
    writeUInt32BE(b, this.oldPosition, offset);
    offset += 4;
    writeUInt32BE(b, this.newPosition, offset);
    offset += 4;
    offset = TextChange._writeString(b, this.oldText, offset);
    offset = TextChange._writeString(b, this.newText, offset);
    return offset;
  }
  static read(b, offset, dest) {
    const oldPosition = readUInt32BE(b, offset);
    offset += 4;
    const newPosition = readUInt32BE(b, offset);
    offset += 4;
    const oldText = TextChange._readString(b, offset);
    offset += TextChange._writeStringSize(oldText);
    const newText = TextChange._readString(b, offset);
    offset += TextChange._writeStringSize(newText);
    dest.push(new TextChange(oldPosition, oldText, newPosition, newText));
    return offset;
  }
}
function compressConsecutiveTextChanges(prevEdits, currEdits) {
  if (prevEdits === null || prevEdits.length === 0) {
    return currEdits;
  }
  const compressor = new TextChangeCompressor(prevEdits, currEdits);
  return compressor.compress();
}
class TextChangeCompressor {
  constructor(prevEdits, currEdits) {
    this._prevEdits = prevEdits;
    this._currEdits = currEdits;
    this._result = [];
    this._resultLen = 0;
    this._prevLen = this._prevEdits.length;
    this._prevDeltaOffset = 0;
    this._currLen = this._currEdits.length;
    this._currDeltaOffset = 0;
  }
  compress() {
    let prevIndex = 0;
    let currIndex = 0;
    let prevEdit = this._getPrev(prevIndex);
    let currEdit = this._getCurr(currIndex);
    while (prevIndex < this._prevLen || currIndex < this._currLen) {
      if (prevEdit === null) {
        this._acceptCurr(currEdit);
        currEdit = this._getCurr(++currIndex);
        continue;
      }
      if (currEdit === null) {
        this._acceptPrev(prevEdit);
        prevEdit = this._getPrev(++prevIndex);
        continue;
      }
      if (currEdit.oldEnd <= prevEdit.newPosition) {
        this._acceptCurr(currEdit);
        currEdit = this._getCurr(++currIndex);
        continue;
      }
      if (prevEdit.newEnd <= currEdit.oldPosition) {
        this._acceptPrev(prevEdit);
        prevEdit = this._getPrev(++prevIndex);
        continue;
      }
      if (currEdit.oldPosition < prevEdit.newPosition) {
        const [e1, e2] = TextChangeCompressor._splitCurr(currEdit, prevEdit.newPosition - currEdit.oldPosition);
        this._acceptCurr(e1);
        currEdit = e2;
        continue;
      }
      if (prevEdit.newPosition < currEdit.oldPosition) {
        const [e1, e2] = TextChangeCompressor._splitPrev(prevEdit, currEdit.oldPosition - prevEdit.newPosition);
        this._acceptPrev(e1);
        prevEdit = e2;
        continue;
      }
      let mergePrev;
      let mergeCurr;
      if (currEdit.oldEnd === prevEdit.newEnd) {
        mergePrev = prevEdit;
        mergeCurr = currEdit;
        prevEdit = this._getPrev(++prevIndex);
        currEdit = this._getCurr(++currIndex);
      } else if (currEdit.oldEnd < prevEdit.newEnd) {
        const [e1, e2] = TextChangeCompressor._splitPrev(prevEdit, currEdit.oldLength);
        mergePrev = e1;
        mergeCurr = currEdit;
        prevEdit = e2;
        currEdit = this._getCurr(++currIndex);
      } else {
        const [e1, e2] = TextChangeCompressor._splitCurr(currEdit, prevEdit.newLength);
        mergePrev = prevEdit;
        mergeCurr = e1;
        prevEdit = this._getPrev(++prevIndex);
        currEdit = e2;
      }
      this._result[this._resultLen++] = new TextChange(mergePrev.oldPosition, mergePrev.oldText, mergeCurr.newPosition, mergeCurr.newText);
      this._prevDeltaOffset += mergePrev.newLength - mergePrev.oldLength;
      this._currDeltaOffset += mergeCurr.newLength - mergeCurr.oldLength;
    }
    const merged = TextChangeCompressor._merge(this._result);
    const cleaned = TextChangeCompressor._removeNoOps(merged);
    return cleaned;
  }
  _acceptCurr(currEdit) {
    this._result[this._resultLen++] = TextChangeCompressor._rebaseCurr(this._prevDeltaOffset, currEdit);
    this._currDeltaOffset += currEdit.newLength - currEdit.oldLength;
  }
  _getCurr(currIndex) {
    return currIndex < this._currLen ? this._currEdits[currIndex] : null;
  }
  _acceptPrev(prevEdit) {
    this._result[this._resultLen++] = TextChangeCompressor._rebasePrev(this._currDeltaOffset, prevEdit);
    this._prevDeltaOffset += prevEdit.newLength - prevEdit.oldLength;
  }
  _getPrev(prevIndex) {
    return prevIndex < this._prevLen ? this._prevEdits[prevIndex] : null;
  }
  static _rebaseCurr(prevDeltaOffset, currEdit) {
    return new TextChange(currEdit.oldPosition - prevDeltaOffset, currEdit.oldText, currEdit.newPosition, currEdit.newText);
  }
  static _rebasePrev(currDeltaOffset, prevEdit) {
    return new TextChange(prevEdit.oldPosition, prevEdit.oldText, prevEdit.newPosition + currDeltaOffset, prevEdit.newText);
  }
  static _splitPrev(edit, offset) {
    const preText = edit.newText.substr(0, offset);
    const postText = edit.newText.substr(offset);
    return [
      new TextChange(edit.oldPosition, edit.oldText, edit.newPosition, preText),
      new TextChange(edit.oldEnd, "", edit.newPosition + offset, postText)
    ];
  }
  static _splitCurr(edit, offset) {
    const preText = edit.oldText.substr(0, offset);
    const postText = edit.oldText.substr(offset);
    return [
      new TextChange(edit.oldPosition, preText, edit.newPosition, edit.newText),
      new TextChange(edit.oldPosition + offset, postText, edit.newEnd, "")
    ];
  }
  static _merge(edits) {
    if (edits.length === 0) {
      return edits;
    }
    const result = [];
    let resultLen = 0;
    let prev = edits[0];
    for (let i = 1; i < edits.length; i++) {
      const curr = edits[i];
      if (prev.oldEnd === curr.oldPosition) {
        prev = new TextChange(prev.oldPosition, prev.oldText + curr.oldText, prev.newPosition, prev.newText + curr.newText);
      } else {
        result[resultLen++] = prev;
        prev = curr;
      }
    }
    result[resultLen++] = prev;
    return result;
  }
  static _removeNoOps(edits) {
    if (edits.length === 0) {
      return edits;
    }
    const result = [];
    let resultLen = 0;
    for (let i = 0; i < edits.length; i++) {
      const edit = edits[i];
      if (edit.oldText === edit.newText) {
        continue;
      }
      result[resultLen++] = edit;
    }
    return result;
  }
}
export {
  TextChange,
  compressConsecutiveTextChanges
};
