import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { $createTextNode, LexicalEditor, TextNode } from 'lexical';
import { useEffect } from 'react';

export class SectionHeadingNode extends TextNode {
  static getType(): string {
    return 'sectionHeading';
  }

  static clone(node: SectionHeadingNode): SectionHeadingNode {
    return new SectionHeadingNode(node.__text, node.__key);
  }

  createDOM(): HTMLElement {
    const dom = document.createElement('span');
    dom.className = 'section-heading';
    dom.innerText = this.__text;

    return dom;
  }
}

const $createSectionHeadingNode = (text: string): SectionHeadingNode => {
  return new SectionHeadingNode(text);
};

function findHeading(text: string): { position: number; text: string } | null {
  let startIndex = -1;
  let endIndex = -1;
  for (let i = 0; i < text.length; i++) {
    if (text[i] === '[') {
      startIndex = i;
    }
    if (startIndex > -1 && text[i] === ']') {
      endIndex = i;
    }
    if (startIndex >= 0 && endIndex >= 0) {
      return {
        position: startIndex,
        text: text.slice(startIndex, endIndex + 1),
      };
    }
  }
  return null;
}

function textNodeTransform(node: TextNode): void {
  if (!node.isSimpleText() || node.hasFormat('code')) {
    return;
  }

  const text = node.getTextContent();

  const headingMatch = findHeading(text);
  if (headingMatch === null) {
    return;
  }

  let targetNode;
  if (headingMatch.position === 0) {
    [targetNode] = node.splitText(
      headingMatch.position + headingMatch.text.length
    );
  } else {
    [, targetNode] = node.splitText(
      headingMatch.position,
      headingMatch.position + headingMatch.text.length
    );
  }

  const headingNode = $createSectionHeadingNode(headingMatch.text);
  targetNode.replace(headingNode);
}

function sectionHeadingNodeTransform(node: SectionHeadingNode): void {
  const text = node.getTextContent();
  const headingMatch = findHeading(text);

  let targetNode1, targetNode2;
  if (headingMatch === null) {
    const textNode = $createTextNode(text);
    node.replace(textNode);
    return;
  } else if (headingMatch.position === 0) {
    [, targetNode1] = node.splitText(
      headingMatch.position + headingMatch.text.length
    );
  } else {
    [targetNode1, , targetNode2] = node.splitText(
      headingMatch.position,
      headingMatch.position + headingMatch.text.length
    );
  }

  if (targetNode1) {
    const textNode = $createTextNode(targetNode1.getTextContent());
    targetNode1.replace(textNode);
  }
  if (targetNode2) {
    const textNode = $createTextNode(targetNode2.getTextContent());
    targetNode2.replace(textNode);
  }
}

export function registerSectionHeading(editor: LexicalEditor): () => void {
  // We don't use editor.registerUpdateListener here as alternative approach where we rely
  // on update listener is highly discouraged as it triggers an additional render (the most expensive lifecycle operation).
  const undoers = [
    editor.registerNodeTransform(TextNode, textNodeTransform),
    editor.registerNodeTransform(
      SectionHeadingNode,
      sectionHeadingNodeTransform
    ),
  ];

  return () => {
    undoers.forEach((undo) => undo());
  };
}

export const SectionHeadingPlugin = () => {
  const [editor] = useLexicalComposerContext();
  useEffect(() => {
    if (!editor.hasNodes([SectionHeadingNode])) {
      throw new Error(
        'SectionHeadingPlugin: SectionHeadingNode not registered on editor'
      );
    }

    return registerSectionHeading(editor);
  });
  return null;
};
