/**
 * 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 lexical = require('lexical');

/**
 * 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.
 *
 */

// Do not require this module directly! Use normal `invariant` calls.

function formatDevErrorMessage(message) {
  throw new Error(message);
}

function $getLastMatchingCodeNode(anchor, direction) {
  let matchingNode = anchor;
  for (let caret = lexical.$getSiblingCaret(anchor, direction); caret && ($isCodeHighlightNode(caret.origin) || lexical.$isTabNode(caret.origin)); caret = caret.getAdjacentCaret()) {
    matchingNode = caret.origin;
  }
  return matchingNode;
}
function $getFirstCodeNodeOfLine(anchor) {
  return $getLastMatchingCodeNode(anchor, 'previous');
}
function $getLastCodeNodeOfLine(anchor) {
  return $getLastMatchingCodeNode(anchor, 'next');
}

/**
 * Determines the visual writing direction of a code line.
 *
 * Scans the line segments (CodeHighlightNode/TabNode) from start to end
 * and returns the first strong direction found ("ltr" or "rtl").
 * If no strong character is found, falls back to the parent element's
 * direction. Returns null if indeterminate.
 */
function $getCodeLineDirection(anchor) {
  const start = $getFirstCodeNodeOfLine(anchor);
  const end = $getLastCodeNodeOfLine(anchor);
  let node = start;
  while (node !== null) {
    if ($isCodeHighlightNode(node)) {
      const direction = lexical.getTextDirection(node.getTextContent());
      if (direction !== null) {
        return direction;
      }
    }
    if (node === end) {
      break;
    }
    node = node.getNextSibling();
  }
  const parent = start.getParent();
  if (lexical.$isElementNode(parent)) {
    const parentDirection = parent.getDirection();
    if (parentDirection === 'ltr' || parentDirection === 'rtl') {
      return parentDirection;
    }
  }
  return null;
}
function $getStartOfCodeInLine(anchor, offset) {
  let last = null;
  let lastNonBlank = null;
  let node = anchor;
  let nodeOffset = offset;
  let nodeTextContent = anchor.getTextContent();
  while (true) {
    if (nodeOffset === 0) {
      node = node.getPreviousSibling();
      if (node === null) {
        break;
      }
      if (!($isCodeHighlightNode(node) || lexical.$isTabNode(node) || lexical.$isLineBreakNode(node))) {
        formatDevErrorMessage(`Expected a valid Code Node: CodeHighlightNode, TabNode, LineBreakNode`);
      }
      if (lexical.$isLineBreakNode(node)) {
        last = {
          node,
          offset: 1
        };
        break;
      }
      nodeOffset = Math.max(0, node.getTextContentSize() - 1);
      nodeTextContent = node.getTextContent();
    } else {
      nodeOffset--;
    }
    const character = nodeTextContent[nodeOffset];
    if ($isCodeHighlightNode(node) && character !== ' ') {
      lastNonBlank = {
        node,
        offset: nodeOffset
      };
    }
  }
  // lastNonBlank !== null: anchor in the middle of code; move to line beginning
  if (lastNonBlank !== null) {
    return lastNonBlank;
  }
  // Spaces, tabs or nothing ahead of anchor
  let codeCharacterAtAnchorOffset = null;
  if (offset < anchor.getTextContentSize()) {
    if ($isCodeHighlightNode(anchor)) {
      codeCharacterAtAnchorOffset = anchor.getTextContent()[offset];
    }
  } else {
    const nextSibling = anchor.getNextSibling();
    if ($isCodeHighlightNode(nextSibling)) {
      codeCharacterAtAnchorOffset = nextSibling.getTextContent()[0];
    }
  }
  if (codeCharacterAtAnchorOffset !== null && codeCharacterAtAnchorOffset !== ' ') {
    // Borderline whitespace and code, move to line beginning
    return last;
  } else {
    const nextNonBlank = findNextNonBlankInLine(anchor, offset);
    if (nextNonBlank !== null) {
      return nextNonBlank;
    } else {
      return last;
    }
  }
}
function findNextNonBlankInLine(anchor, offset) {
  let node = anchor;
  let nodeOffset = offset;
  let nodeTextContent = anchor.getTextContent();
  let nodeTextContentSize = anchor.getTextContentSize();
  while (true) {
    if (!$isCodeHighlightNode(node) || nodeOffset === nodeTextContentSize) {
      node = node.getNextSibling();
      if (node === null || lexical.$isLineBreakNode(node)) {
        return null;
      }
      if ($isCodeHighlightNode(node)) {
        nodeOffset = 0;
        nodeTextContent = node.getTextContent();
        nodeTextContentSize = node.getTextContentSize();
      }
    }
    if ($isCodeHighlightNode(node)) {
      if (nodeTextContent[nodeOffset] !== ' ') {
        return {
          node,
          offset: nodeOffset
        };
      }
      nodeOffset++;
    }
  }
}
function $getEndOfCodeInLine(anchor) {
  const lastNode = $getLastCodeNodeOfLine(anchor);
  if (!!lexical.$isLineBreakNode(lastNode)) {
    formatDevErrorMessage(`Unexpected lineBreakNode in getEndOfCodeInLine`);
  }
  return lastNode;
}

/**
 * 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.
 *
 */

const DEFAULT_CODE_LANGUAGE = 'javascript';
const getDefaultCodeLanguage = () => DEFAULT_CODE_LANGUAGE;
function hasChildDOMNodeTag(node, tagName) {
  for (const child of node.childNodes) {
    if (lexical.isHTMLElement(child) && child.tagName === tagName) {
      return true;
    }
    hasChildDOMNodeTag(child, tagName);
  }
  return false;
}
const LANGUAGE_DATA_ATTRIBUTE = 'data-language';
const HIGHLIGHT_LANGUAGE_DATA_ATTRIBUTE = 'data-highlight-language';
const THEME_DATA_ATTRIBUTE = 'data-theme';

/** @noInheritDoc */
class CodeNode extends lexical.ElementNode {
  /** @internal */
  __language;
  /** @internal */
  __theme;
  /** @internal */
  __isSyntaxHighlightSupported;
  static getType() {
    return 'code';
  }
  static clone(node) {
    return new CodeNode(node.__language, node.__key);
  }
  constructor(language, key) {
    super(key);
    this.__language = language || undefined;
    this.__isSyntaxHighlightSupported = false;
    this.__theme = undefined;
  }
  afterCloneFrom(prevNode) {
    super.afterCloneFrom(prevNode);
    this.__language = prevNode.__language;
    this.__theme = prevNode.__theme;
    this.__isSyntaxHighlightSupported = prevNode.__isSyntaxHighlightSupported;
  }

  // View
  createDOM(config) {
    const element = document.createElement('code');
    lexical.addClassNamesToElement(element, config.theme.code);
    element.setAttribute('spellcheck', 'false');
    const language = this.getLanguage();
    if (language) {
      element.setAttribute(LANGUAGE_DATA_ATTRIBUTE, language);
      if (this.getIsSyntaxHighlightSupported()) {
        element.setAttribute(HIGHLIGHT_LANGUAGE_DATA_ATTRIBUTE, language);
      }
    }
    const theme = this.getTheme();
    if (theme) {
      element.setAttribute(THEME_DATA_ATTRIBUTE, theme);
    }
    const style = this.getStyle();
    if (style) {
      element.setAttribute('style', style);
    }
    return element;
  }
  updateDOM(prevNode, dom, config) {
    const language = this.__language;
    const prevLanguage = prevNode.__language;
    if (language) {
      if (language !== prevLanguage) {
        dom.setAttribute(LANGUAGE_DATA_ATTRIBUTE, language);
      }
    } else if (prevLanguage) {
      dom.removeAttribute(LANGUAGE_DATA_ATTRIBUTE);
    }
    const isSyntaxHighlightSupported = this.__isSyntaxHighlightSupported;
    const prevIsSyntaxHighlightSupported = prevNode.__isSyntaxHighlightSupported;
    if (prevIsSyntaxHighlightSupported && prevLanguage) {
      if (isSyntaxHighlightSupported && language) {
        if (language !== prevLanguage) {
          dom.setAttribute(HIGHLIGHT_LANGUAGE_DATA_ATTRIBUTE, language);
        }
      } else {
        dom.removeAttribute(HIGHLIGHT_LANGUAGE_DATA_ATTRIBUTE);
      }
    } else if (isSyntaxHighlightSupported && language) {
      dom.setAttribute(HIGHLIGHT_LANGUAGE_DATA_ATTRIBUTE, language);
    }
    const theme = this.__theme;
    const prevTheme = prevNode.__theme;
    if (theme) {
      if (theme !== prevTheme) {
        dom.setAttribute(THEME_DATA_ATTRIBUTE, theme);
      }
    } else if (prevTheme) {
      dom.removeAttribute(THEME_DATA_ATTRIBUTE);
    }
    const style = this.__style;
    const prevStyle = prevNode.__style;
    if (style) {
      if (style !== prevStyle) {
        dom.setAttribute('style', style);
      }
    } else if (prevStyle) {
      dom.removeAttribute('style');
    }
    return false;
  }
  exportDOM(editor) {
    const element = document.createElement('pre');
    lexical.addClassNamesToElement(element, editor._config.theme.code);
    element.setAttribute('spellcheck', 'false');
    const language = this.getLanguage();
    if (language) {
      element.setAttribute(LANGUAGE_DATA_ATTRIBUTE, language);
      if (this.getIsSyntaxHighlightSupported()) {
        element.setAttribute(HIGHLIGHT_LANGUAGE_DATA_ATTRIBUTE, language);
      }
    }
    const theme = this.getTheme();
    if (theme) {
      element.setAttribute(THEME_DATA_ATTRIBUTE, theme);
    }
    const style = this.getStyle();
    if (style) {
      element.setAttribute('style', style);
    }
    return {
      element
    };
  }
  static importDOM() {
    return {
      // Typically <pre> is used for code blocks, and <code> for inline code styles
      // but if it's a multi line <code> we'll create a block. Pass through to
      // inline format handled by TextNode otherwise.
      code: node => {
        const isMultiLine = node.textContent != null && (/\r?\n/.test(node.textContent) || hasChildDOMNodeTag(node, 'BR'));
        return isMultiLine ? {
          conversion: $convertPreElement,
          priority: 1
        } : null;
      },
      div: () => ({
        conversion: $convertDivElement,
        priority: 1
      }),
      pre: () => ({
        conversion: $convertPreElement,
        priority: 0
      }),
      table: node => {
        const table = node;
        // domNode is a <table> since we matched it by nodeName
        if (isGitHubCodeTable(table)) {
          return {
            conversion: $convertTableElement,
            priority: 3
          };
        }
        return null;
      },
      td: node => {
        // element is a <td> since we matched it by nodeName
        const td = node;
        const table = td.closest('table');
        if (isGitHubCodeCell(td) || table && isGitHubCodeTable(table)) {
          // Return a no-op if it's a table cell in a code table, but not a code line.
          // Otherwise it'll fall back to the T
          return {
            conversion: convertCodeNoop,
            priority: 3
          };
        }
        return null;
      },
      tr: node => {
        // element is a <tr> since we matched it by nodeName
        const tr = node;
        const table = tr.closest('table');
        if (table && isGitHubCodeTable(table)) {
          return {
            conversion: convertCodeNoop,
            priority: 3
          };
        }
        return null;
      }
    };
  }
  static importJSON(serializedNode) {
    return $createCodeNode().updateFromJSON(serializedNode);
  }
  updateFromJSON(serializedNode) {
    return super.updateFromJSON(serializedNode).setLanguage(serializedNode.language).setTheme(serializedNode.theme);
  }
  exportJSON() {
    return {
      ...super.exportJSON(),
      language: this.getLanguage(),
      theme: this.getTheme()
    };
  }

  // Mutation
  insertNewAfter(selection, restoreSelection = true) {
    const children = this.getChildren();
    const childrenLength = children.length;
    if (childrenLength >= 2 && children[childrenLength - 1].getTextContent() === '\n' && children[childrenLength - 2].getTextContent() === '\n' && selection.isCollapsed() && selection.anchor.key === this.__key && selection.anchor.offset === childrenLength) {
      children[childrenLength - 1].remove();
      children[childrenLength - 2].remove();
      const newElement = lexical.$createParagraphNode();
      this.insertAfter(newElement, restoreSelection);
      return newElement;
    }

    // If the selection is within the codeblock, find all leading tabs and
    // spaces of the current line. Create a new line that has all those
    // tabs and spaces, such that leading indentation is preserved.
    const {
      anchor,
      focus
    } = selection;
    const firstPoint = anchor.isBefore(focus) ? anchor : focus;
    const firstSelectionNode = firstPoint.getNode();
    if (lexical.$isTextNode(firstSelectionNode)) {
      let node = $getFirstCodeNodeOfLine(firstSelectionNode);
      const insertNodes = [];
      while (true) {
        if (lexical.$isTabNode(node)) {
          insertNodes.push(lexical.$createTabNode());
          node = node.getNextSibling();
        } else if ($isCodeHighlightNode(node)) {
          let spaces = 0;
          const text = node.getTextContent();
          const textSize = node.getTextContentSize();
          while (spaces < textSize && text[spaces] === ' ') {
            spaces++;
          }
          if (spaces !== 0) {
            insertNodes.push($createCodeHighlightNode(' '.repeat(spaces)));
          }
          if (spaces !== textSize) {
            break;
          }
          node = node.getNextSibling();
        } else {
          break;
        }
      }
      const split = firstSelectionNode.splitText(anchor.offset)[0];
      const x = anchor.offset === 0 ? 0 : 1;
      const index = split.getIndexWithinParent() + x;
      const codeNode = firstSelectionNode.getParentOrThrow();
      const nodesToInsert = [lexical.$createLineBreakNode(), ...insertNodes];
      codeNode.splice(index, 0, nodesToInsert);
      const last = insertNodes[insertNodes.length - 1];
      if (last) {
        last.select();
      } else if (anchor.offset === 0) {
        split.selectPrevious();
      } else {
        split.getNextSibling().selectNext(0, 0);
      }
    }
    if ($isCodeNode(firstSelectionNode)) {
      const {
        offset
      } = selection.anchor;
      firstSelectionNode.splice(offset, 0, [lexical.$createLineBreakNode()]);
      firstSelectionNode.select(offset + 1, offset + 1);
    }
    return null;
  }
  canIndent() {
    return false;
  }
  collapseAtStart() {
    const paragraph = lexical.$createParagraphNode();
    const children = this.getChildren();
    children.forEach(child => paragraph.append(child));
    this.replace(paragraph);
    return true;
  }
  setLanguage(language) {
    const writable = this.getWritable();
    writable.__language = language || undefined;
    return writable;
  }
  getLanguage() {
    return this.getLatest().__language;
  }
  setIsSyntaxHighlightSupported(isSupported) {
    const writable = this.getWritable();
    writable.__isSyntaxHighlightSupported = isSupported;
    return writable;
  }
  getIsSyntaxHighlightSupported() {
    return this.getLatest().__isSyntaxHighlightSupported;
  }
  setTheme(theme) {
    const writable = this.getWritable();
    writable.__theme = theme || undefined;
    return writable;
  }
  getTheme() {
    return this.getLatest().__theme;
  }
}
function $createCodeNode(language, theme) {
  return lexical.$create(CodeNode).setLanguage(language).setTheme(theme);
}
function $isCodeNode(node) {
  return node instanceof CodeNode;
}
function $convertPreElement(domNode) {
  const language = domNode.getAttribute(LANGUAGE_DATA_ATTRIBUTE);
  return {
    node: $createCodeNode(language)
  };
}
function $convertDivElement(domNode) {
  // domNode is a <div> since we matched it by nodeName
  const div = domNode;
  const isCode = isCodeElement(div);
  if (!isCode && !isCodeChildElement(div)) {
    return {
      node: null
    };
  }
  return {
    node: isCode ? $createCodeNode() : null
  };
}
function $convertTableElement() {
  return {
    node: $createCodeNode()
  };
}
function convertCodeNoop() {
  return {
    node: null
  };
}
function isCodeElement(div) {
  return div.style.fontFamily.match('monospace') !== null;
}
function isCodeChildElement(node) {
  let parent = node.parentElement;
  while (parent !== null) {
    if (isCodeElement(parent)) {
      return true;
    }
    parent = parent.parentElement;
  }
  return false;
}
function isGitHubCodeCell(cell) {
  return cell.classList.contains('js-file-line');
}
function isGitHubCodeTable(table) {
  return table.classList.contains('js-file-line-container');
}

/**
 * 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.
 *
 */

/** @noInheritDoc */
class CodeHighlightNode extends lexical.TextNode {
  /** @internal */
  __highlightType;
  constructor(text = '', highlightType, key) {
    super(text, key);
    this.__highlightType = highlightType;
  }
  static getType() {
    return 'code-highlight';
  }
  static clone(node) {
    return new CodeHighlightNode(node.__text, node.__highlightType || undefined, node.__key);
  }
  afterCloneFrom(prevNode) {
    super.afterCloneFrom(prevNode);
    this.__highlightType = prevNode.__highlightType;
  }
  getHighlightType() {
    const self = this.getLatest();
    return self.__highlightType;
  }
  setHighlightType(highlightType) {
    const self = this.getWritable();
    self.__highlightType = highlightType || undefined;
    return self;
  }
  canHaveFormat() {
    return false;
  }
  createDOM(config) {
    const element = super.createDOM(config);
    const className = getHighlightThemeClass(config.theme, this.__highlightType);
    lexical.addClassNamesToElement(element, className);
    return element;
  }
  updateDOM(prevNode, dom, config) {
    const update = super.updateDOM(prevNode, dom, config);
    const prevClassName = getHighlightThemeClass(config.theme, prevNode.__highlightType);
    const nextClassName = getHighlightThemeClass(config.theme, this.__highlightType);
    if (prevClassName !== nextClassName) {
      if (prevClassName) {
        lexical.removeClassNamesFromElement(dom, prevClassName);
      }
      if (nextClassName) {
        lexical.addClassNamesToElement(dom, nextClassName);
      }
    }
    return update;
  }
  static importJSON(serializedNode) {
    return $createCodeHighlightNode().updateFromJSON(serializedNode);
  }
  updateFromJSON(serializedNode) {
    return super.updateFromJSON(serializedNode).setHighlightType(serializedNode.highlightType);
  }
  exportJSON() {
    return {
      ...super.exportJSON(),
      highlightType: this.getHighlightType()
    };
  }

  // Prevent formatting (bold, underline, etc)
  setFormat(format) {
    return this;
  }
  isParentRequired() {
    return true;
  }
  createParentElementNode() {
    return $createCodeNode();
  }
}
function getHighlightThemeClass(theme, highlightType) {
  return highlightType && theme && theme.codeHighlight && theme.codeHighlight[highlightType];
}
function $createCodeHighlightNode(text = '', highlightType) {
  return lexical.$applyNodeReplacement(new CodeHighlightNode(text, highlightType));
}
function $isCodeHighlightNode(node) {
  return node instanceof CodeHighlightNode;
}

/**
 * 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.
 *
 */

/**
 * Add code blocks to the editor (syntax highlighting provided separately)
 */
const CodeExtension = lexical.defineExtension({
  name: '@lexical/code',
  nodes: () => [CodeNode, CodeHighlightNode]
});

exports.$createCodeHighlightNode = $createCodeHighlightNode;
exports.$createCodeNode = $createCodeNode;
exports.$getCodeLineDirection = $getCodeLineDirection;
exports.$getEndOfCodeInLine = $getEndOfCodeInLine;
exports.$getFirstCodeNodeOfLine = $getFirstCodeNodeOfLine;
exports.$getLastCodeNodeOfLine = $getLastCodeNodeOfLine;
exports.$getStartOfCodeInLine = $getStartOfCodeInLine;
exports.$isCodeHighlightNode = $isCodeHighlightNode;
exports.$isCodeNode = $isCodeNode;
exports.CodeExtension = CodeExtension;
exports.CodeHighlightNode = CodeHighlightNode;
exports.CodeNode = CodeNode;
exports.DEFAULT_CODE_LANGUAGE = DEFAULT_CODE_LANGUAGE;
exports.getDefaultCodeLanguage = getDefaultCodeLanguage;
