import * as Y from 'yjs';

/**
 * Lightweight Y.Text ↔ textarea binding.
 * Observes Y.Text changes → updates textarea value.
 * Listens to textarea input events → applies delta to Y.Text.
 * Preserves cursor position across remote updates.
 */
export class YTextareaBinding {
  private yText: Y.Text;
  private textarea: HTMLTextAreaElement;
  private observer: (event: Y.YTextEvent) => void;
  private inputHandler: (e: Event) => void;
  private destroyed = false;

  constructor(yText: Y.Text, textarea: HTMLTextAreaElement) {
    this.yText = yText;
    this.textarea = textarea;

    // Initialize textarea with current Y.Text content
    textarea.value = yText.toString();

    // Observe Y.Text changes (remote updates) → update textarea
    this.observer = () => {
      if (this.destroyed) return;
      const newVal = yText.toString();
      if (textarea.value === newVal) return;

      // Save cursor position and adjust for changes
      const cursorBefore = textarea.selectionStart;
      const lengthBefore = textarea.value.length;
      textarea.value = newVal;
      const lengthAfter = newVal.length;

      // Adjust cursor: shift by the length difference if cursor was after the change
      const delta = lengthAfter - lengthBefore;
      const newCursor = Math.max(0, Math.min(cursorBefore + delta, lengthAfter));
      textarea.selectionStart = newCursor;
      textarea.selectionEnd = newCursor;
    };
    yText.observe(this.observer);

    // Listen to textarea input events → apply delta to Y.Text
    this.inputHandler = () => {
      if (this.destroyed) return;
      const newVal = textarea.value;
      const oldVal = yText.toString();
      if (newVal === oldVal) return;

      // Find the changed region

      // Find common prefix length
      let prefixLen = 0;
      const minLen = Math.min(oldVal.length, newVal.length);
      while (prefixLen < minLen && oldVal[prefixLen] === newVal[prefixLen]) {
        prefixLen++;
      }

      // Find common suffix length (don't overlap with prefix)
      let suffixLen = 0;
      while (
        suffixLen < (oldVal.length - prefixLen) &&
        suffixLen < (newVal.length - prefixLen) &&
        oldVal[oldVal.length - 1 - suffixLen] === newVal[newVal.length - 1 - suffixLen]
      ) {
        suffixLen++;
      }

      const deleteCount = oldVal.length - prefixLen - suffixLen;
      const insertText = newVal.slice(prefixLen, newVal.length - suffixLen);

      // Apply changes to Y.Text in a transaction
      yText.doc?.transact(() => {
        if (deleteCount > 0) {
          yText.delete(prefixLen, deleteCount);
        }
        if (insertText.length > 0) {
          yText.insert(prefixLen, insertText);
        }
      });
    };
    textarea.addEventListener('input', this.inputHandler);
  }

  destroy(): void {
    this.destroyed = true;
    this.yText.unobserve(this.observer);
    this.textarea.removeEventListener('input', this.inputHandler);
  }
}
