/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
'use strict';

var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
var richText = require('@lexical/rich-text');
var lexical = require('lexical');
var react = require('react');

/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

function $insertHeadingIntoTableOfContents(prevHeading, newHeading, currentTableOfContents) {
  if (newHeading === null) {
    return currentTableOfContents;
  }

  const newEntry = [newHeading.getKey(), newHeading.getTextContent(), newHeading.getTag()];
  let newTableOfContents = [];

  if (prevHeading === null) {
    newTableOfContents = [newEntry, ...currentTableOfContents];
  } else {
    for (let i = 0; i < currentTableOfContents.length; i++) {
      const key = currentTableOfContents[i][0];
      newTableOfContents.push(currentTableOfContents[i]);

      if (key === prevHeading.getKey() && key !== newHeading.getKey()) {
        newTableOfContents.push(newEntry);
      }
    }
  }

  return newTableOfContents;
}

function $deleteHeadingFromTableOfContents(key, currentTableOfContents) {
  const newTableOfContents = [];

  for (const heading of currentTableOfContents) {
    if (heading[0] !== key) {
      newTableOfContents.push(heading);
    }
  }

  return newTableOfContents;
}

function $updateHeadingInTableOfContents(heading, currentTableOfContents) {
  const newTextContent = heading.getTextContent();
  const newTableOfContents = [];

  for (const oldHeading of currentTableOfContents) {
    if (oldHeading[0] === heading.getKey()) {
      newTableOfContents.push([heading.getKey(), newTextContent, heading.getTag()]);
    } else {
      newTableOfContents.push(oldHeading);
    }
  }

  return newTableOfContents;
}

function LexicalTableOfContentsPlugin({
  children
}) {
  const [tableOfContents, setTableOfContents] = react.useState([]);
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
  react.useEffect(() => {
    // Set table of contents initial state
    let currentTableOfContents = [];
    editor.getEditorState().read(() => {
      const root = lexical.$getRoot();
      const rootChildren = root.getChildren();

      for (const child of rootChildren) {
        if (richText.$isHeadingNode(child)) {
          currentTableOfContents.push([child.getKey(), child.getTextContent(), child.getTag()]);
        }
      }

      setTableOfContents(currentTableOfContents);
    }); // Listen to updates to heading mutations and update state

    const removeHeaderMutationListener = editor.registerMutationListener(richText.HeadingNode, mutatedNodes => {
      editor.getEditorState().read(() => {
        for (const [nodeKey, mutation] of mutatedNodes) {
          if (mutation === 'created') {
            const newHeading = lexical.$getNodeByKey(nodeKey);

            if (newHeading !== null) {
              let prevHeading = newHeading.getPreviousSibling();

              while (prevHeading && !richText.$isHeadingNode(prevHeading)) {
                prevHeading = prevHeading.getPreviousSibling();
              }

              currentTableOfContents = $insertHeadingIntoTableOfContents(prevHeading, newHeading, currentTableOfContents);
              setTableOfContents(currentTableOfContents);
            }
          } else if (mutation === 'destroyed') {
            currentTableOfContents = $deleteHeadingFromTableOfContents(nodeKey, currentTableOfContents);
            setTableOfContents(currentTableOfContents);
          }
        }
      });
    }); // Listen to text node mutation updates

    const removeTextNodeMutationListener = editor.registerMutationListener(lexical.TextNode, mutatedNodes => {
      editor.getEditorState().read(() => {
        for (const [nodeKey, mutation] of mutatedNodes) {
          if (mutation === 'updated') {
            const currNode = lexical.$getNodeByKey(nodeKey);

            if (currNode !== null) {
              const parentNode = currNode.getParentOrThrow();

              if (richText.$isHeadingNode(parentNode)) {
                currentTableOfContents = $updateHeadingInTableOfContents(parentNode, currentTableOfContents);
                setTableOfContents(currentTableOfContents);
              }
            }
          }
        }
      });
    });
    return () => {
      removeHeaderMutationListener();
      removeTextNodeMutationListener();
    };
  }, [editor]);
  return children(tableOfContents, editor);
}

module.exports = LexicalTableOfContentsPlugin;
