import {
  Editor,
  Node,
  Path,
  Range,
  Text,
  Element,
  BasePoint,
  BaseRange,
  Point,
} from "slate";
import { EditorAutocompleteState } from "../types/base/editor-autocomplete-state";

export interface EditorTextState {
  selection: BaseRange;

  textBeforeCursor: string;
  selectedText: string;
  textAfterCursor: string;
}

export function getTextAroundCollapsedCursor(
  editor: Editor,
): EditorAutocompleteState | null {
  const { selection } = editor;
  if (!selection || !Range.isCollapsed(selection)) {
    return null;
  }

  const cursorPoint = selection.anchor;

  // Create two ranges: one before the anchor and one after
  const beforeRange: Range = {
    anchor: Editor.start(editor, []),
    focus: cursorPoint,
  };
  const afterRange: Range = {
    anchor: cursorPoint,
    focus: Editor.end(editor, []),
  };

  // Extract text for these ranges
  const before = extractTextWithNewlines(editor, beforeRange);
  const after = extractTextWithNewlines(editor, afterRange);

  return {
    cursorPoint: cursorPoint,
    textBeforeCursor: before,
    textAfterCursor: after,
  };
}

export function getTextAroundSelection(editor: Editor): EditorTextState | null {
  const { selection } = editor;
  if (!selection) {
    return null;
  }

  const wellOrderedSelection = wellOrderedRange(selection);

  // Create two ranges: one before the anchor and one after
  const beforeRange: Range = {
    anchor: Editor.start(editor, []),
    focus: wellOrderedSelection.anchor,
  };
  const afterRange: Range = {
    anchor: wellOrderedSelection.focus,
    focus: Editor.end(editor, []),
  };

  // Extract text for these ranges
  const before = extractTextWithNewlines(editor, beforeRange);
  const after = extractTextWithNewlines(editor, afterRange);
  const selectedText = extractTextWithNewlines(editor, wellOrderedSelection);

  return {
    selection: wellOrderedSelection,
    textBeforeCursor: before,
    selectedText,
    textAfterCursor: after,
  };
}

export function getFullEditorTextWithNewlines(editor: Editor): string {
  const fullDocumentRange: Range = {
    anchor: Editor.start(editor, []),
    focus: Editor.end(editor, []),
  };
  return extractTextWithNewlines(editor, fullDocumentRange);
}

// Helper function to extract text with newlines
export function extractTextWithNewlines(editor: Editor, range: Range): string {
  const voids = false;
  const [start, end] = Range.edges(range);
  let text = "";
  let lastBlock: Node | null = null;

  for (const [node, path] of Editor.nodes(editor, {
    at: range,
    match: Text.isText,
    voids,
  })) {
    let t = node.text;

    // Determine the parent block of the current text node
    const [block] = Editor.above(editor, {
      at: path,
      match: (n) => Element.isElement(n) && n.type === "paragraph",
    }) || [null];

    // If we encounter a new block, prepend a newline
    if (lastBlock !== block && block) {
      // check that lastBlock is not null to avoid adding a newline at the beginning
      if (lastBlock) {
        text += "\n";
      }
      lastBlock = block;
    }

    if (Path.equals(path, end.path)) {
      t = t.slice(0, end.offset);
    }

    if (Path.equals(path, start.path)) {
      t = t.slice(start.offset);
    }

    text += t;
  }

  return text;
}

function wellOrderedRange(range: BaseRange): BaseRange {
  const { anchor, focus } = range;
  // if anchor is before focus, return range as is
  if (Point.isBefore(anchor, focus)) {
    return range;
  }

  // if focus is before anchor, return range with anchor and focus swapped
  return {
    anchor: focus,
    focus: anchor,
  };
}
