/**
 * A class to help with text selection and manipulation of textareas and inputs
 */
export class TextSelection {
  elm: HTMLTextAreaElement;
  start: number;
  end: number;
  value: string;

  constructor(elm: HTMLTextAreaElement) {
    const { selectionStart, selectionEnd } = elm;
    this.elm = elm;
    this.start = selectionStart;
    this.end = selectionEnd;
    this.value = this.elm.value;
  }
  /**
   * Sets the position of the text selection within an element.
   *
   * @param {number} [start] - The start position of the selection.
   * @param {number} [end] - The end position of the selection.
   * @returns {Object} - Returns the instance of the object.
   */
  position(start?: number, end?: number) {
    const { selectionStart, selectionEnd } = this.elm;
    this.start =
      typeof start === 'number' && !isNaN(start) ? start : selectionStart;
    this.end = typeof end === 'number' && !isNaN(end) ? end : selectionEnd;
    this.elm.selectionStart = this.start;
    this.elm.selectionEnd = this.end;
    return this;
  }

  /**
   * Inserts text at the current cursor position
   * @param text the text to insert
   * @returns the TextSelection instance
   */
  insertText(text: string) {
    // Most of the used APIs only work with the field selected
    this.elm.focus();
    this.elm.setRangeText(text);
    this.value = this.elm.value;
    this.position();
    return this;
  }

  /**
   * Gets the selected text value from a textarea / input
   * @param start number start index
   * @param end number end index
   * @returns the selected text
   */
  getSelectedValue(start?: number, end?: number) {
    const { selectionStart, selectionEnd } = this.elm;
    return this.value.slice(
      typeof start === 'number' && !isNaN(start) ? start : selectionStart,
      typeof end === 'number' && !isNaN(end) ? end : selectionEnd
    );
  }

  /**
   * Gets the start index of the current line where the text is selected
   * @returns the start index of the current line
   */
  getLineStartNumber() {
    let start = this.start;
    while (start > 0) {
      start--;
      if (this.value.charAt(start) === '\n') {
        start++;
        break;
      }
    }
    return start;
  }

  /** Indent on new lines */
  getIndentText() {
    const start = this.getLineStartNumber();
    const str = this.getSelectedValue(start);
    let indent = '';
    str.replace(/(^(\s)+)/, (_str, old) => (indent = old));
    return indent;
  }

  lineStarInsert(text: string) {
    if (text) {
      const oldStart = this.start;
      const start = this.getLineStartNumber();
      const str = this.getSelectedValue(start);
      this.position(start, this.end)
        .insertText(
          str
            .split('\n')
            .map((txt) => text + txt)
            .join('\n')
        )
        .position(oldStart + text.length, this.end);
    }
    return this;
  }

  lineStarRemove(text: string) {
    if (text) {
      const oldStart = this.start;
      const start = this.getLineStartNumber();
      const str = this.getSelectedValue(start);
      const reg = new RegExp(`^${text}`, 'g');
      let newStart = oldStart - text.length;
      if (!reg.test(str)) {
        newStart = oldStart;
      }
      this.position(start, this.end)
        .insertText(
          str
            .split('\n')
            .map((txt) => txt.replace(reg, ''))
            .join('\n')
        )
        .position(newStart, this.end + newStart - oldStart);
    }
  }

  /** Notify any possible listeners of the change */
  notifyChange() {
    const event = new Event('input', { bubbles: true, cancelable: false });
    this.elm.dispatchEvent(event);
  }
}
