/**
 * 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.
 *
 * @flow strict
 */

/**
 * LexicalCommands
 */

export type LexicalCommand<P> = $ReadOnly<{type?: string}>;

declare export var SELECTION_CHANGE_COMMAND: LexicalCommand<void>;
declare export var CLICK_COMMAND: LexicalCommand<MouseEvent>;
declare export var DELETE_CHARACTER_COMMAND: LexicalCommand<boolean>;
declare export var INSERT_LINE_BREAK_COMMAND: LexicalCommand<boolean>;
declare export var INSERT_PARAGRAPH_COMMAND: LexicalCommand<void>;
declare export var CONTROLLED_TEXT_INSERTION_COMMAND: LexicalCommand<string>;
declare export var PASTE_COMMAND: LexicalCommand<ClipboardEvent>;
declare export var REMOVE_TEXT_COMMAND: LexicalCommand<InputEvent | null>;
declare export var DELETE_WORD_COMMAND: LexicalCommand<boolean>;
declare export var DELETE_LINE_COMMAND: LexicalCommand<boolean>;
declare export var FORMAT_TEXT_COMMAND: LexicalCommand<TextFormatType>;
declare export var UNDO_COMMAND: LexicalCommand<void>;
declare export var REDO_COMMAND: LexicalCommand<void>;
declare export var KEY_DOWN_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_ARROW_RIGHT_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_ARROW_LEFT_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_ARROW_UP_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_ARROW_DOWN_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_ENTER_COMMAND: LexicalCommand<KeyboardEvent | null>;
declare export var KEY_SPACE_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_BACKSPACE_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_ESCAPE_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_DELETE_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_TAB_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var INSERT_TAB_COMMAND: LexicalCommand<void>;
declare export var KEY_MODIFIER_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var INDENT_CONTENT_COMMAND: LexicalCommand<void>;
declare export var OUTDENT_CONTENT_COMMAND: LexicalCommand<void>;
declare export var DROP_COMMAND: LexicalCommand<DragEvent>;
declare export var FORMAT_ELEMENT_COMMAND: LexicalCommand<ElementFormatType>;
declare export var DRAGSTART_COMMAND: LexicalCommand<DragEvent>;
declare export var DRAGOVER_COMMAND: LexicalCommand<DragEvent>;
declare export var DRAGEND_COMMAND: LexicalCommand<DragEvent>;
declare export var COPY_COMMAND: LexicalCommand<
  ClipboardEvent | KeyboardEvent | null,
>;
declare export var CUT_COMMAND: LexicalCommand<
  ClipboardEvent | KeyboardEvent | null,
>;
declare export var CLEAR_EDITOR_COMMAND: LexicalCommand<void>;
declare export var CLEAR_HISTORY_COMMAND: LexicalCommand<void>;
declare export var CAN_REDO_COMMAND: LexicalCommand<boolean>;
declare export var CAN_UNDO_COMMAND: LexicalCommand<boolean>;
declare export var FOCUS_COMMAND: LexicalCommand<FocusEvent>;
declare export var BLUR_COMMAND: LexicalCommand<FocusEvent>;
declare export var SELECT_ALL_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var MOVE_TO_END: LexicalCommand<KeyboardEvent>;
declare export var MOVE_TO_START: LexicalCommand<KeyboardEvent>;
declare export var SELECTION_INSERT_CLIPBOARD_NODES_COMMAND: LexicalCommand<{
  nodes: Array<LexicalNode>;
  selection: BaseSelection;
}>;

declare export function createCommand<T>(type?: string): LexicalCommand<T>;

/**
 * LexicalConstants
 */

declare export var IS_ALL_FORMATTING: number;
declare export var IS_BOLD: number;
declare export var IS_CODE: number;
declare export var IS_HIGHLIGHT: number;
declare export var IS_ITALIC: number;
declare export var IS_STRIKETHROUGH: number;
declare export var IS_SUBSCRIPT: number;
declare export var IS_SUPERSCRIPT: number;
declare export var IS_UNDERLINE: number;
declare export var IS_UPPERCASE: number;
declare export var IS_LOWERCASE: number;
declare export var IS_CAPITALIZE: number;
declare export var TEXT_TYPE_TO_FORMAT: Record<TextFormatType | string, number>;

/**
 * LexicalEditor
 */
type IntentionallyMarkedAsDirtyElement = boolean;

type MutationListeners = Map<MutationListener, Class<LexicalNode>>;
export type NodeMutation = 'created' | 'updated' | 'destroyed';
export type UpdateListenerPayload = {
  tags: Set<string>,
  prevEditorState: EditorState,
  editorState: EditorState,
  dirtyLeaves: Set<NodeKey>,
  dirtyElements: Map<NodeKey, IntentionallyMarkedAsDirtyElement>,
  normalizedNodes: Set<NodeKey>,
};
export type UpdateListener = (payload: UpdateListenerPayload) => void;
type DecoratorListener = (decorator: {
  // $FlowFixMe[unclear-type]: defined by user
  [NodeKey]: any,
}) => void;
type RootListener = (
  rootElement: null | HTMLElement,
  prevRootElement: null | HTMLElement,
) => void;
type TextContentListener = (text: string) => void;
type ErrorHandler = (error: Error) => void;
export type MutationListener = (
  nodes: Map<NodeKey, NodeMutation>,
  {
    updateTags: Set<string>,
    dirtyLeaves: Set<string>,
    prevEditorState: EditorState,
  },
) => void;
export type MutationListenerOptions = {
  skipInitialization?: boolean;
};
export type EditableListener = (editable: boolean) => void;
type Listeners = {
  decorator: Set<DecoratorListener>,
  mutation: MutationListeners,
  textcontent: Set<TextContentListener>,
  root: Set<RootListener>,
  update: Set<UpdateListener>,
};
export type CommandListener<P> = (payload: P, editor: LexicalEditor) => boolean;
// $FlowFixMe[unclear-type]
type Commands = Map<LexicalCommand<any>, Array<Set<CommandListener<any>>>>;
type RegisteredNodes = Map<string, RegisteredNode>;
type RegisteredNode = {
  klass: Class<LexicalNode>,
  transforms: Set<Transform<LexicalNode>>,
};
export type Transform<T> = (node: T) => void;

type DOMConversionCache = Map<
  string,
  Array<(node: Node) => DOMConversion | null>,
>;

export type CreateEditorArgs = {
  disableEvents?: boolean;
  editorState?: EditorState;
  namespace?: string;
  nodes?: $ReadOnlyArray<Class<LexicalNode> | LexicalNodeReplacement>;
  onError?: ErrorHandler;
  parentEditor?: LexicalEditor;
  editable?: boolean;
  theme?: EditorThemeClasses;
  html?: HTMLConfig;
};

declare export class LexicalEditor {
  _parentEditor: null | LexicalEditor;
  _rootElement: null | HTMLElement;
  _editorState: EditorState;
  _htmlConversions: DOMConversionCache;
  _pendingEditorState: null | EditorState;
  _compositionKey: null | NodeKey;
  _deferred: Array<() => void>;
  _updates: Array<[() => void, void | EditorUpdateOptions]>;
  _updating: boolean;
  _keyToDOMMap: Map<NodeKey, HTMLElement>;
  _listeners: Listeners;
  _commands: Commands;
  _nodes: RegisteredNodes;
  _onError: ErrorHandler;
  _decorators: {
    [NodeKey]: mixed,
  };
  _pendingDecorators: null | {
    [NodeKey]: mixed,
  };
  _createEditorArgs?: CreateEditorArgs;
  _config: EditorConfig;
  _dirtyType: 0 | 1 | 2;
  _cloneNotNeeded: Set<NodeKey>;
  _dirtyLeaves: Set<NodeKey>;
  _dirtyElements: Map<NodeKey, IntentionallyMarkedAsDirtyElement>;
  _normalizedNodes: Set<NodeKey>;
  _updateTags: Set<UpdateTag>;
  _observer: null | MutationObserver;
  _key: string;
  _editable: boolean;
  _headless: boolean;
  isComposing(): boolean;
  registerUpdateListener(listener: UpdateListener): () => void;
  registerRootListener(listener: RootListener): () => void;
  registerDecoratorListener(listener: DecoratorListener): () => void;
  registerTextContentListener(listener: TextContentListener): () => void;
  registerCommand<P>(
    command: LexicalCommand<P>,
    listener: CommandListener<P>,
    priority: CommandListenerPriority,
  ): () => void;
  registerEditableListener(listener: EditableListener): () => void;
  registerMutationListener(
    klass: Class<LexicalNode>,
    listener: MutationListener,
    options?: MutationListenerOptions,
  ): () => void;
  registerNodeTransform<T: LexicalNode>(
    klass: Class<T>,
    listener: Transform<T>,
  ): () => void;
  dispatchCommand<P>(command: LexicalCommand<P>, payload: P): boolean;
  hasNode(node: Class<LexicalNode>): boolean;
  hasNodes(nodes: Array<Class<LexicalNode>>): boolean;
  getKey(): string;
  getDecorators<X>(): {
    [NodeKey]: X,
  };
  getRootElement(): null | HTMLElement;
  setRootElement(rootElement: null | HTMLElement): void;
  getElementByKey(key: NodeKey): null | HTMLElement;
  getEditorState(): EditorState;
  setEditorState(editorState: EditorState, options?: EditorSetOptions): void;
  parseEditorState(
    maybeStringifiedEditorState: string | SerializedEditorState,
    updateFn?: () => void,
  ): EditorState;
  read<V>(callbackFn: () => V, options?: EditorReadOptions): V;
  update(updateFn: () => void, options?: EditorUpdateOptions): boolean;
  focus(callbackFn?: () => void, options?: EditorFocusOptions): void;
  blur(): void;
  isEditable(): boolean;
  setEditable(editable: boolean): void;
  toJSON(): SerializedEditor;
}
type EditorReadOptions = {
  pending?: boolean,
};
export type EditorUpdateOptions = {
  onUpdate?: () => void,
  tag?: string | Array<string>,
  skipTransforms?: true,
  discrete?: true,
};
type EditorFocusOptions = {
  defaultSelection?: 'rootStart' | 'rootEnd',
};
export type EditorSetOptions = {
  tag?: string,
};
type EditorThemeClassName = string;
type TextNodeThemeClasses = {
  base?: EditorThemeClassName,
  bold?: EditorThemeClassName,
  underline?: EditorThemeClassName,
  strikethrough?: EditorThemeClassName,
  underlineStrikethrough?: EditorThemeClassName,
  italic?: EditorThemeClassName,
  code?: EditorThemeClassName,
  subscript?: EditorThemeClassName,
  superscript?: EditorThemeClassName,
  lowercase?: EditorThemeClassName,
  uppercase?: EditorThemeClassName,
  capitalize?: EditorThemeClassName,
};
export type EditorThemeClasses = {
  characterLimit?: EditorThemeClassName,
  ltr?: EditorThemeClassName,
  rtl?: EditorThemeClassName,
  text?: TextNodeThemeClasses,
  paragraph?: EditorThemeClassName,
  image?: EditorThemeClassName,
  list?: {
    ul?: EditorThemeClassName,
    ulDepth?: Array<EditorThemeClassName>,
    ol?: EditorThemeClassName,
    olDepth?: Array<EditorThemeClassName>,
    checklist?: EditorThemeClassName,
    listitem?: EditorThemeClassName,
    listitemChecked?: EditorThemeClassName,
    listitemUnchecked?: EditorThemeClassName,
    nested?: {
      list?: EditorThemeClassName,
      listitem?: EditorThemeClassName,
    },
  },
  table?: EditorThemeClassName,
  tableRow?: EditorThemeClassName,
  tableCell?: EditorThemeClassName,
  tableCellHeader?: EditorThemeClassName,
  mark?: EditorThemeClassName,
  markOverlap?: EditorThemeClassName,
  link?: EditorThemeClassName,
  quote?: EditorThemeClassName,
  code?: EditorThemeClassName,
  codeHighlight?: {[string]: EditorThemeClassName},
  hashtag?: EditorThemeClassName,
  heading?: {
    h1?: EditorThemeClassName,
    h2?: EditorThemeClassName,
    h3?: EditorThemeClassName,
    h4?: EditorThemeClassName,
    h5?: EditorThemeClassName,
    h6?: EditorThemeClassName,
  },
  embedBlock?: {
    base?: EditorThemeClassName,
    focus?: EditorThemeClassName,
  },
  // Handle other generic values
  [string]: EditorThemeClassName | {[string]: EditorThemeClassName},
};
export type EditorConfig = {
  theme: EditorThemeClasses,
  namespace: string,
  disableEvents?: boolean,
};
export type CommandListenerPriority = 0 | 1 | 2 | 3 | 4;
export const COMMAND_PRIORITY_EDITOR = 0;
export const COMMAND_PRIORITY_LOW = 1;
export const COMMAND_PRIORITY_NORMAL = 2;
export const COMMAND_PRIORITY_HIGH = 3;
export const COMMAND_PRIORITY_CRITICAL = 4;

export type LexicalNodeReplacement = {
  replace: Class<LexicalNode>,
  with: (node: LexicalNode) => LexicalNode,
  withKlass?: Class<LexicalNode>,
};

export type HTMLConfig = {
  export?: Map<
    Class<LexicalNode>,
    (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput,
  >,
  import?: DOMConversionMap,
};

declare export function createEditor(editorConfig?: {
  editorState?: EditorState,
  namespace: string,
  theme?: EditorThemeClasses,
  parentEditor?: LexicalEditor,
  nodes?: $ReadOnlyArray<Class<LexicalNode> | LexicalNodeReplacement>,
  onError: (error: Error) => void,
  disableEvents?: boolean,
  editable?: boolean,
  html?: HTMLConfig,
}): LexicalEditor;

/**
 * LexicalEditorState
 */

export interface EditorState {
  _nodeMap: NodeMap;
  _selection: null | BaseSelection;
  _flushSync: boolean;
  _readOnly: boolean;
  constructor(nodeMap: NodeMap, selection?: BaseSelection | null): void;
  isEmpty(): boolean;
  read<V>(callbackFn: () => V, options?: EditorStateReadOptions): V;
  toJSON(): SerializedEditorState;
  clone(selection?: BaseSelection | null): EditorState;
}
type EditorStateReadOptions = {
  editor?: LexicalEditor | null;
}

/**
 * LexicalNode
 */

export type DOMConversion = {
  conversion: DOMConversionFn,
  priority: 0 | 1 | 2 | 3 | 4,
};
export type DOMConversionFn = (element: Node) => DOMConversionOutput | null;
export type DOMChildConversion = (
  lexicalNode: LexicalNode,
  parentLexicalNode: ?LexicalNode | null,
) => LexicalNode | null | void;
export type DOMConversionMap = {
  [NodeName]: <T: HTMLElement>(node: T) => DOMConversion | null,
};
type NodeName = string;
export type DOMConversionOutput = {
  after?: (childLexicalNodes: Array<LexicalNode>) => Array<LexicalNode>,
  forChild?: DOMChildConversion,
  node: null | LexicalNode | Array<LexicalNode>,
};
export type DOMExportOutput = {
  after?: (generatedElement: ?HTMLElement) => ?HTMLElement,
  element?: HTMLElement | null,
};
export type NodeKey = string;
declare export class LexicalNode {
  __type: string;
  __key: NodeKey;
  __parent: null | NodeKey;
  __next: null | NodeKey;
  __prev: null | NodeKey;
  static getType(): string;
  static clone(data: $FlowFixMe): LexicalNode;
  static importDOM(): DOMConversionMap | null;
  constructor(key?: NodeKey): void;
  exportDOM(editor: LexicalEditor): DOMExportOutput;
  exportJSON(): SerializedLexicalNode;
  updateFromJSON(serializedNode: $FlowFixMe): this;
  getType(): string;
  isAttached(): boolean;
  isSelected(): boolean;
  getKey(): NodeKey;
  getIndexWithinParent(): number;
  getParent<T: ElementNode>(): T | null;
  getParentOrThrow<T: ElementNode>(): T;
  getTopLevelElement(): DecoratorNode<mixed> | ElementNode | null;
  getTopLevelElementOrThrow(): DecoratorNode<mixed> | ElementNode;
  getParents<T: ElementNode>(): Array<T>;
  getParentKeys(): Array<NodeKey>;
  getPreviousSibling<T: LexicalNode>(): T | null;
  getPreviousSiblings<T: LexicalNode>(): Array<T>;
  getNextSibling<T: LexicalNode>(): T | null;
  getNextSiblings<T: LexicalNode>(): Array<T>;
  getCommonAncestor<T: ElementNode>(node: LexicalNode): T | null;
  is(object: ?LexicalNode): boolean;
  isBefore(targetNode: LexicalNode): boolean;
  isParentOf(targetNode: LexicalNode): boolean;
  getNodesBetween(targetNode: LexicalNode): Array<LexicalNode>;
  isDirty(): boolean;
  // $FlowFixMe[incompatible-type]
  getLatest<T: LexicalNode>(this: T): T;
  // $FlowFixMe[incompatible-type]
  getWritable<T: LexicalNode>(this: T): T;
  getTextContent(includeDirectionless?: boolean): string;
  getTextContentSize(includeDirectionless?: boolean): number;
  createDOM(config: EditorConfig, editor: LexicalEditor): HTMLElement;
  updateDOM(
    // $FlowFixMe[unclear-type]
    prevNode: any,
    dom: HTMLElement,
    config: EditorConfig,
  ): boolean;
  remove(preserveEmptyParent?: boolean): void;
  replace<N: LexicalNode>(replaceWith: N): N;
  insertAfter(
    nodeToInsert: LexicalNode,
    restoreSelection?: boolean,
  ): LexicalNode;
  insertBefore(
    nodeToInsert: LexicalNode,
    restoreSelection?: boolean,
  ): LexicalNode;
  selectPrevious(anchorOffset?: number, focusOffset?: number): RangeSelection;
  selectNext(anchorOffset?: number, focusOffset?: number): RangeSelection;
  markDirty(): void;
  reconcileObservedMutation(dom: HTMLElement, editor: LexicalEditor): void;
}
export type NodeMap = Map<NodeKey, LexicalNode>;

/**
 * LexicalSelection
 */

declare export function $isBlockElementNode(
  node: ?LexicalNode,
): node is ElementNode;

export interface BaseSelection {
  dirty: boolean;
  clone(): BaseSelection;
  extract(): Array<LexicalNode>;
  getNodes(): Array<LexicalNode>;
  getStartEndPoints(): null | [PointType, PointType];
  getTextContent(): string;
  insertRawText(text: string): void;
  is(selection: null | BaseSelection): boolean;
  isBackward(): boolean;
  isCollapsed(): boolean;
  insertText(text: string): void;
  insertNodes(nodes: Array<LexicalNode>): void;
  getCachedNodes(): null | Array<LexicalNode>;
  setCachedNodes(nodes: null | Array<LexicalNode>): void;
}

declare export class NodeSelection implements BaseSelection {
  _nodes: Set<NodeKey>;
  dirty: boolean;
  constructor(objects: Set<NodeKey>): void;
  is(selection: null | BaseSelection): boolean;
  isBackward(): boolean;
  isCollapsed(): boolean;
  add(key: NodeKey): void;
  delete(key: NodeKey): void;
  clear(): void;
  has(key: NodeKey): boolean;
  clone(): NodeSelection;
  extract(): Array<LexicalNode>;
  insertRawText(): void;
  insertText(): void;
  getNodes(): Array<LexicalNode>;
  getStartEndPoints(): null;
  getTextContent(): string;
  insertNodes(nodes: Array<LexicalNode>): void;
  getCachedNodes(): null | Array<LexicalNode>;
  setCachedNodes(nodes: null | Array<LexicalNode>): void;
}

declare export function $isNodeSelection(
  x: ?mixed,
): x is NodeSelection;

declare export class RangeSelection implements BaseSelection {
  anchor: PointType;
  focus: PointType;
  dirty: boolean;
  format: number;
  style: string;
  constructor(anchor: PointType, focus: PointType, format: number): void;
  is(selection: null | BaseSelection): boolean;
  isBackward(): boolean;
  isCollapsed(): boolean;
  getNodes(): Array<LexicalNode>;
  setTextNodeRange(
    anchorNode: TextNode,
    anchorOffset: number,
    focusNode: TextNode,
    focusOffset: number,
  ): void;
  getTextContent(): string;
  // $FlowFixMe[cannot-resolve-name] DOM API
  applyDOMRange(range: StaticRange): void;
  clone(): RangeSelection;
  toggleFormat(format: TextFormatType): void;
  setStyle(style: string): void;
  hasFormat(type: TextFormatType): boolean;
  insertText(text: string): void;
  insertRawText(text: string): void;
  removeText(): void;
  formatText(formatType: TextFormatType): void;
  insertNodes(nodes: Array<LexicalNode>): void;
  insertParagraph(): void;
  insertLineBreak(selectStart?: boolean): void;
  extract(): Array<LexicalNode>;
  modify(
    alter: 'move' | 'extend',
    isBackward: boolean,
    granularity: 'character' | 'word' | 'lineboundary',
  ): void;
  deleteCharacter(isBackward: boolean): void;
  deleteLine(isBackward: boolean): void;
  deleteWord(isBackward: boolean): void;
  insertNodes(nodes: Array<LexicalNode>): void;
  getCachedNodes(): null | Array<LexicalNode>;
  setCachedNodes(nodes: null | Array<LexicalNode>): void;
  forwardDeletion(anchor: PointType, anchorNode: ElementNode | TextNode, isBackward: boolean): boolean;
  getStartEndPoints(): null | [PointType, PointType];
}
export type TextPoint = TextPointType;
type TextPointType = {
  key: NodeKey,
  offset: number,
  type: 'text',
  is: (PointType) => boolean,
  isBefore: (PointType) => boolean,
  getNode: () => TextNode,
  set: (key: NodeKey, offset: number, type: 'text' | 'element') => void,
  getCharacterOffset: () => number,
};
export type ElementPoint = ElementPointType;
type ElementPointType = {
  key: NodeKey,
  offset: number,
  type: 'element',
  is: (PointType) => boolean,
  isBefore: (PointType) => boolean,
  getNode: () => ElementNode,
  set: (key: NodeKey, offset: number, type: 'text' | 'element') => void,
};
export type Point = PointType;
export type PointType = TextPointType | ElementPointType;
declare class _Point {
  key: NodeKey;
  offset: number;
  type: 'text' | 'element';
  constructor(key: NodeKey, offset: number, type: 'text' | 'element'): void;
  is(point: PointType): boolean;
  isBefore(b: PointType): boolean;
  getNode(): LexicalNode;
  set(key: NodeKey, offset: number, type: 'text' | 'element', onlyIfChanged?: boolean): void;
}

declare export function $createRangeSelection(): RangeSelection;
declare export function $createNodeSelection(): NodeSelection;
declare export function $isRangeSelection(
  x: ?mixed,
): x is RangeSelection;
declare export function $getSelection(): null | BaseSelection;
declare export function $getPreviousSelection(): null | BaseSelection;
declare export function $insertNodes(nodes: Array<LexicalNode>): void;
declare export function $getCharacterOffsets(
  selection: BaseSelection,
): [number, number];


/**
 * LexicalTextNode
 */

export type TextFormatType =
  | 'bold'
  | 'underline'
  | 'strikethrough'
  | 'italic'
  | 'highlight'
  | 'code'
  | 'subscript'
  | 'superscript'
  | 'lowercase'
  | 'uppercase'
  | 'capitalize';

type TextModeType = 'normal' | 'token' | 'segmented';

declare export class TextNode extends LexicalNode {
  __text: string;
  __format: number;
  __style: string;
  __mode: 0 | 1 | 2 | 3;
  __detail: number;
  static getType(): string;
  static clone(node: $FlowFixMe): TextNode;
  constructor(text: string, key?: NodeKey): void;
  getTopLevelElement(): ElementNode | null;
  getTopLevelElementOrThrow(): ElementNode;
  getFormat(): number;
  getStyle(): string;
  isComposing(): boolean;
  isInline(): true;
  isToken(): boolean;
  isSegmented(): boolean;
  isDirectionless(): boolean;
  isUnmergeable(): boolean;
  hasFormat(type: TextFormatType): boolean;
  isSimpleText(): boolean;
  getTextContent(): string;
  getFormatFlags(type: TextFormatType, alignWithFormat: null | number): number;
  createDOM(config: EditorConfig): HTMLElement;
  selectionTransform(
    prevSelection: null | BaseSelection,
    nextSelection: RangeSelection,
  ): void;
  setFormat(format: number): this;
  setStyle(style: string): this;
  toggleFormat(type: TextFormatType): TextNode;
  toggleDirectionless(): this;
  toggleUnmergeable(): this;
  setMode(type: TextModeType): this;
  setDetail(detail: number): this;
  getDetail(): number;
  getMode(): TextModeType;
  setTextContent(text: string): TextNode;
  select(_anchorOffset?: number, _focusOffset?: number): RangeSelection;
  spliceText(
    offset: number,
    delCount: number,
    newText: string,
    moveSelection?: boolean,
  ): TextNode;
  canInsertTextBefore(): boolean;
  canInsertTextAfter(): boolean;
  splitText(...splitOffsets: Array<number>): Array<TextNode>;
  mergeWithSibling(target: TextNode): TextNode;
  isTextEntity(): boolean;
  static importJSON(serializedTextNode: SerializedTextNode): TextNode;
  exportJSON(): SerializedTextNode;
}
declare export function $createTextNode(text?: string): TextNode;
declare export function $isTextNode(
  node: ?LexicalNode,
): node is TextNode;

/**
 * LexicalTabNode
 */

export type SerializedTabNode = SerializedTextNode;

declare export function $createTabNode(): TabNode;

declare export function $isTabNode(
  node: LexicalNode | null | void,
): node is TabNode;

declare export class TabNode extends TextNode {
  static getType(): string;
  static clone(node: TabNode): TabNode;
  constructor(key?: NodeKey): void;
  static importDOM(): DOMConversionMap | null;
  static importJSON(serializedTabNode: SerializedTabNode): TabNode;
  exportJSON(): SerializedTabNode;
}

/**
 * LexicalLineBreakNode
 */

declare export class LineBreakNode extends LexicalNode {
  static getType(): string;
  static clone(node: LineBreakNode): LineBreakNode;
  constructor(key?: NodeKey): void;
  getTextContent(): '\n';
  createDOM(): HTMLElement;
  updateDOM(): false;
  isInline(): true;
  static importJSON(
    serializedLineBreakNode: SerializedLineBreakNode,
  ): LineBreakNode;
  exportJSON(): SerializedLexicalNode;
}
declare export function $createLineBreakNode(): LineBreakNode;
declare export function $isLineBreakNode(
  node: ?LexicalNode,
): node is LineBreakNode;

/**
 * LexicalRootNode
 */

declare export class RootNode extends ElementNode {
  __cachedText: null | string;
  static getType(): string;
  static clone(): RootNode;
  constructor(): void;
  getTextContent(): string;
  select(_anchorOffset?: number, _focusOffset?: number): RangeSelection;
  remove(): void;
  replace<N: LexicalNode>(node: N): N;
  insertBefore<T: LexicalNode>(nodeToInsert: T): T;
  insertAfter<T: LexicalNode>(nodeToInsert: T): T;
  append(...nodesToAppend: Array<LexicalNode>): this;
  canBeEmpty(): false;
}
declare export function $isRootNode(
  node: ?LexicalNode,
): node is RootNode;

/**
 * LexicalElementNode
 */
export type ElementFormatType =
  | 'left'
  | 'start'
  | 'center'
  | 'right'
  | 'end'
  | 'justify'
  | '';
declare export class ElementNode extends LexicalNode {
  __first: null | NodeKey;
  __last: null | NodeKey;
  __size: number;
  __format: number;
  __indent: number;
  __dir: 'ltr' | 'rtl' | null;
  constructor(key?: NodeKey): void;
  getTopLevelElement(): ElementNode | null;
  getTopLevelElementOrThrow(): ElementNode;
  getFormat(): number;
  getFormatType(): ElementFormatType;
  getIndent(): number;
  getChildren<T: LexicalNode>(): Array<T>;
  getChildren<T: Array<LexicalNode>>(): T;
  getChildrenKeys(): Array<NodeKey>;
  getChildrenSize(): number;
  isEmpty(): boolean;
  isDirty(): boolean;
  getAllTextNodes(): Array<TextNode>;
  getFirstDescendant<T: LexicalNode>(): null | T;
  getLastDescendant<T: LexicalNode>(): null | T;
  getDescendantByIndex<T: LexicalNode>(index: number): null | T;
  getFirstChild<T: LexicalNode>(): null | T;
  getFirstChildOrThrow<T: LexicalNode>(): T;
  getLastChild<T: LexicalNode>(): null | T;
  getLastChildOrThrow<T: LexicalNode>(): T;
  getChildAtIndex<T: LexicalNode>(index: number): null | T;
  getTextContent(): string;
  getDirection(): 'ltr' | 'rtl' | null;
  hasFormat(type: ElementFormatType): boolean;
  select(_anchorOffset?: number, _focusOffset?: number): RangeSelection;
  selectStart(): RangeSelection;
  selectEnd(): RangeSelection;
  clear(): this;
  append(...nodesToAppend: Array<LexicalNode>): this;
  setDirection(direction: 'ltr' | 'rtl' | null): this;
  setFormat(type: ElementFormatType): this;
  setIndent(indentLevel: number): this;
  insertNewAfter(
    selection: RangeSelection,
    restoreSelection?: boolean,
  ): null | LexicalNode;
  canIndent(): boolean;
  collapseAtStart(selection: RangeSelection): boolean;
  excludeFromCopy(destination: 'clone' | 'html'): boolean;
  canReplaceWith(replacement: LexicalNode): boolean;
  canInsertAfter(node: LexicalNode): boolean;
  extractWithChild(
    child: LexicalNode,
    selection: BaseSelection,
    destination: 'clone' | 'html',
  ): boolean;
  canBeEmpty(): boolean;
  canInsertTextBefore(): boolean;
  canInsertTextAfter(): boolean;
  isInline(): boolean;
  isShadowRoot(): boolean;
  canSelectionRemove(): boolean;
  splice(
    start: number,
    deleteCount: number,
    nodesToInsert: Array<LexicalNode>,
  ): this;
  exportJSON(): SerializedElementNode;
  getDOMSlot(dom: HTMLElement): ElementDOMSlot<HTMLElement>;
}
declare export function $isElementNode(
  node: ?LexicalNode,
): node is ElementNode;

/**
 * ElementDOMSlot
 */
declare export class ElementDOMSlot<+T: HTMLElement> {
  +element: HTMLElement;
  +before: Node | null;
  +after: Node | null;
  constructor(element: HTMLElement, before?: Node | null | void, after?: Node | null | void): void;
  withBefore(before: Node | null | void): ElementDOMSlot<T>;
  withAfter(after: Node | null | void): ElementDOMSlot<T>;
  withElement<ElementType: HTMLElement>(element: ElementType): ElementDOMSlot<ElementType>;
  insertChild(dom: Node): this;
  removeChild(dom: Node): this;
  replaceChild(dom: Node, prevDom: Node): this;
  getFirstChild(): Node | null;
  //
  getManagedLineBreak(): HTMLElement | null;
  removeManagedLineBreak(): void;
  insertManagedLineBreak(webkitHack: boolean): void;
  getFirstChildOffset(): number;
  resolveChildIndex(element: ElementNode, elementDOM: HTMLElement, initialDOM: Node, initialOffset: number): [node: ElementNode, idx: number];
}

declare export function setDOMUnmanaged(elementDOM: HTMLElement): void;
declare export function isDOMUnmanaged(elementDOM: HTMLElement): boolean;

/**
 * LexicalDecoratorNode
 */

declare export class DecoratorNode<X> extends LexicalNode {
  constructor(key?: NodeKey): void;
  // Not sure how to get flow to agree that the DecoratorNode<mixed> is compatible with this,
  // so we have a less precise type than in TS
  // getTopLevelElement(): this | ElementNode | null;
  // getTopLevelElementOrThrow(): this | ElementNode;
  decorate(editor: LexicalEditor, config: EditorConfig): X;
  isIsolated(): boolean;
  isInline(): boolean;
  isKeyboardSelectable(): boolean;
}
declare export function $isDecoratorNode<T = mixed>(
  node: ?LexicalNode,
): node is DecoratorNode<T>;

/**
 * LexicalParagraphNode
 */
declare export class ParagraphNode extends ElementNode {
  static getType(): string;
  static clone(node: ParagraphNode): ParagraphNode;
  constructor(key?: NodeKey): void;
  createDOM(config: EditorConfig): HTMLElement;
  insertNewAfter(
    selection: RangeSelection,
    restoreSelection?: boolean,
  ): ParagraphNode;
  collapseAtStart(): boolean;
  static importJSON(
    serializedParagraphNode: SerializedParagraphNode,
  ): ParagraphNode;
  exportJSON(): SerializedElementNode;
}
declare export function $createParagraphNode(): ParagraphNode;
declare export function $isParagraphNode(
  node: ?LexicalNode,
): node is ParagraphNode;

/**
 * LexicalUtils
 */
export type EventHandler = (event: Event, editor: LexicalEditor) => void;
declare export function $hasUpdateTag(tag: UpdateTag): boolean;
declare export function $addUpdateTag(tag: UpdateTag): void;
declare export function $onUpdate(updateFn: () => void): void;
declare export function getNearestEditorFromDOMNode(
  node: Node | null,
): LexicalEditor | null;
declare export function $getNearestNodeFromDOMNode(
  startingDOM: Node,
): LexicalNode | null;
declare export function $getNodeByKey<N: LexicalNode>(key: NodeKey): N | null;
declare export function $getNodeByKeyOrThrow<N: LexicalNode>(key: NodeKey): N;
declare export function $getRoot(): RootNode;
declare export function $isLeafNode<T = mixed>(
  node: ?LexicalNode,
): node is TextNode | LineBreakNode |  DecoratorNode<T>;
declare export function $setCompositionKey(
  compositionKey: null | NodeKey,
): void;
declare export function $setSelection(selection: null | BaseSelection): void;
declare export function $nodesOfType<T: LexicalNode>(klass: Class<T>): Array<T>;
declare export function $getAdjacentNode(
  focus: Point,
  isBackward: boolean,
): null | LexicalNode;
declare export function resetRandomKey(): void;
declare export function generateRandomKey(): string;
declare export function $isInlineElementOrDecoratorNode<T = mixed>(
  node: LexicalNode,
): node is ElementNode|  DecoratorNode<T>;
declare export function $getNearestRootOrShadowRoot(
  node: LexicalNode,
): RootNode | ElementNode;
declare export function $isRootOrShadowRoot(
  node: ?LexicalNode,
): node is RootNode | ElementNode;
declare export function $hasAncestor(
  child: LexicalNode,
  targetNode: LexicalNode,
): boolean;
declare export function $cloneWithProperties<T: LexicalNode>(node: T): T;
declare export function $copyNode(
  node: ElementNode,
  offset: number,
): [ElementNode, ElementNode];
declare export function $getEditor(): LexicalEditor;

/**
 * LexicalNormalization
 */

declare export function $normalizeSelection__EXPERIMENTAL(
  selection: RangeSelection,
): RangeSelection;

/**
 * Serialization/Deserialization
 * */

type InternalSerializedNode = {
  children?: Array<InternalSerializedNode>,
  type: string,
  version: number,
};

declare export function $parseSerializedNode(
  serializedNode: InternalSerializedNode,
): LexicalNode;

declare export function $applyNodeReplacement<N: LexicalNode>(
  node: LexicalNode,
): N;

export type SerializedLexicalNode = {
  type: string,
  version: number,
  ...
};

export type SerializedTextNode = {
  ...SerializedLexicalNode,
  detail: number,
  format: number,
  mode: TextModeType,
  style: string,
  text: string,
  ...
};

export type SerializedElementNode = {
  ...SerializedLexicalNode,
  children: Array<SerializedLexicalNode>,
  direction: 'ltr' | 'rtl' | null,
  format: ElementFormatType,
  indent: number,
  ...
};

export type SerializedParagraphNode = {
  ...SerializedElementNode,
  ...
};

export type SerializedLineBreakNode = {
  ...SerializedLexicalNode,
  ...
};

export type SerializedDecoratorNode = {
  ...SerializedLexicalNode,
  ...
};

export type SerializedRootNode = {
  ...SerializedElementNode,
  ...
};

export type SerializedGridCellNode = {
  ...SerializedElementNode,
  colSpan: number,
  ...
};

export interface SerializedEditorState {
  root: SerializedRootNode;
}

export type SerializedEditor = {
  editorState: SerializedEditorState,
};

/**
 * LexicalCaret
 */
export interface BaseCaret<T: LexicalNode, D: CaretDirection, Type> extends Iterable<SiblingCaret<LexicalNode, D>> {
  +origin: T;
  +type: Type;
  +direction: D;
  getParentAtCaret(): null | ElementNode;
  getNodeAtCaret(): null | LexicalNode;
  getAdjacentCaret(): null | SiblingCaret<LexicalNode, D>;
  getSiblingCaret(): SiblingCaret<T, D>;
  remove(): BaseCaret<T, D, Type>; // this
  insert(node: LexicalNode): BaseCaret<T, D, Type>; // this
  replaceOrInsert(node: LexicalNode, includeChildren?: boolean): BaseCaret<T, D, Type>; // this
  splice(deleteCount: number, nodes: Iterable<LexicalNode>, nodesDirection?: CaretDirection): BaseCaret<T, D, Type>; // this
}
export type CaretDirection = 'next' | 'previous';
type FLIP_DIRECTION = {'next' : 'previous', 'previous': 'next'};
export interface CaretRange<D: CaretDirection = CaretDirection> extends Iterable<NodeCaret<D>> {
  +type: 'node-caret-range';
  +direction: D;
  anchor: PointCaret<D>;
  focus: PointCaret<D>;
  isCollapsed(): boolean;
  iterNodeCarets(rootMode?: RootMode): Iterable<NodeCaret<D>>;
  getTextSlices(): TextPointCaretSliceTuple<D>;
}
export type CaretType = 'sibling' | 'child';
export interface ChildCaret<T: ElementNode = ElementNode, D: CaretDirection = CaretDirection> extends BaseCaret<T, D, 'child'> {
  getLatest(): ChildCaret<T, D>;
  getParentCaret(mode?: RootMode): null | SiblingCaret<T, D>;
  getParentAtCaret(): T;
  getChildCaret(): ChildCaret<T, D>;
  isSameNodeCaret(other: null | void | PointCaret<CaretDirection>): boolean; // other is ChildCaret<T, D>;
  isSamePointCaret(other: null | void | PointCaret<CaretDirection>): boolean; // other is ChildCaret<T, D>;
  getFlipped(): NodeCaret<FlipDirection<D>>;
  // Refine chained types
  remove(): ChildCaret<T, D>;
  insert(node: LexicalNode): ChildCaret<T, D>;
  replaceOrInsert(node: LexicalNode, includeChildren?: boolean): ChildCaret<T, D>;
  splice(deleteCount: number, nodes: Iterable<LexicalNode>, nodesDirection?: CaretDirection): ChildCaret<T, D>;
}
export type FlipDirection<D: CaretDirection> = FLIP_DIRECTION[D];
export type NodeCaret<D: CaretDirection = CaretDirection> = ChildCaret<ElementNode, D> | SiblingCaret<LexicalNode, D>;
export type PointCaret<D: CaretDirection = CaretDirection> = ChildCaret<ElementNode, D> | SiblingCaret<LexicalNode, D> | TextPointCaret<TextNode, D>;
export type RootMode = 'root' | 'shadowRoot';
export interface SiblingCaret<T: LexicalNode = LexicalNode, D: CaretDirection = CaretDirection> extends BaseCaret<T, D, 'sibling'> {
  getLatest(): SiblingCaret<T, D>;
  getChildCaret(): null | ChildCaret<T & ElementNode, D>;
  getParentCaret(mode?: RootMode): null | SiblingCaret<ElementNode, D>;
  isSameNodeCaret(other: null | void | PointCaret<CaretDirection>): boolean; // </CaretDirection>other is SiblingCaret<T, D> | T extends TextNode ? TextPointCaret<T & TextNode, D> : empty;
  isSamePointCaret(other: null | void | PointCaret<CaretDirection>): boolean; // other is SiblingCaret<T, D>;
  getFlipped(): NodeCaret<FlipDirection<D>>;
  // Refine chained types
  remove(): SiblingCaret<T, D>;
  insert(node: LexicalNode): SiblingCaret<T, D>;
  replaceOrInsert(node: LexicalNode, includeChildren?: boolean): SiblingCaret<T, D>;
  splice(deleteCount: number, nodes: Iterable<LexicalNode>, nodesDirection?: CaretDirection): SiblingCaret<T, D>;
}
export interface StepwiseIteratorConfig<State, Stop, Value> {
  +initial: State | Stop;
  +hasNext: (value: State | Stop) => implies value is State;
  +step: (value: State) => State | Stop;
  +map: (value: State) => Value;
}
export interface TextPointCaret<T: TextNode = TextNode, D: CaretDirection = CaretDirection> extends BaseCaret<T, D, 'text'> {
  +offset: number;
  getLatest(): TextPointCaret<T, D>;
  getChildCaret(): null;
  getParentCaret(): null | SiblingCaret<ElementNode, D>;
  isSameNodeCaret(other: null | void | PointCaret<CaretDirection>): boolean; // other is TextPointCaret<T, D> | SiblingCaret<T, D>;
  isSamePointCaret(other: null | void | PointCaret<CaretDirection>): boolean; // other is TextPointCaret<T, D>;
  getFlipped(): TextPointCaret<T, FlipDirection<D>>;
}
export interface TextPointCaretSlice<T: TextNode = TextNode, D: CaretDirection = CaretDirection> {
  +type: 'slice';
  +caret: TextPointCaret<T, D>;
  +distance: number;
  getSliceIndices(): [startIndex: number, endIndex: number];
  getTextContent(): string;
  getTextContentSize(): number;
  removeTextSlice(): TextPointCaret<T, D>;
}
export type TextPointCaretSliceTuple<D: CaretDirection> = [
  +anchorSlice: null | TextPointCaretSlice<TextNode, D>,
  +focusSlice: null | TextPointCaretSlice<TextNode, D>,
];
declare export function $getAdjacentChildCaret<D: CaretDirection>(caret: null | NodeCaret<D>): null | NodeCaret<D>;
declare export function $getCaretRange<D: CaretDirection>(anchor: PointCaret<D>, focus: PointCaret<D>): CaretRange<D>;
declare export function $getChildCaret<T: null | ElementNode, D: CaretDirection>(origin: T, direction: D): ChildCaret<Exclude<null, T>, D> | Extract<null, T>;
declare export function $getChildCaretOrSelf<Caret: null | PointCaret<CaretDirection>>(caret: Caret): Caret | ChildCaret<ElementNode, Exclude<null, Caret>['direction']>;
declare export function $getSiblingCaret<T: null | LexicalNode, D: CaretDirection>(origin: T, direction: D): SiblingCaret<Exclude<null, T>, D> | Extract<null, T>;
declare export function $getTextNodeOffset(origin: TextNode, offset: number | CaretDirection): number;
declare export function $getTextPointCaret<T: null | TextNode, D: CaretDirection>(origin: T, direction: D, offset: number | CaretDirection): TextPointCaret<Exclude<null, T>, D> | Extract<null, T>;
declare export function $getTextPointCaretSlice<T: TextNode, D: CaretDirection>(caret: TextPointCaret<T, D>, distance: number): TextPointCaretSlice<T, D>;
declare export function $isChildCaret<D: CaretDirection>(caret: null | void | PointCaret<D>): caret is ChildCaret<ElementNode, D>;
declare export function $isNodeCaret<D: CaretDirection>(caret: null | void | PointCaret<D>): caret is NodeCaret<D>;
declare export function $isSiblingCaret<D: CaretDirection>(caret: null | void | PointCaret<D>): caret is SiblingCaret<LexicalNode, D>;
declare export function $isTextPointCaret<D: CaretDirection>(caret: null | void | PointCaret<D>): caret is TextPointCaret<TextNode, D>;
declare export function $isTextPointCaretSlice<D: CaretDirection>(caret: null | void | PointCaret<D> | TextPointCaretSlice<TextNode, D>): caret is TextPointCaretSlice<TextNode, D>;
declare export function flipDirection<D: CaretDirection>(direction: D): FlipDirection<D>;
declare export function makeStepwiseIterator<State, Stop, Value>(
  config: StepwiseIteratorConfig<State, Stop, Value>,
): Iterator<Value>;
/**
 * LexicalCaretUtils
 */
declare export function $caretFromPoint<D: CaretDirection>(
  point: PointType,
  direction: D,
): PointCaret<D>;
declare export function $caretRangeFromSelection(
  selection: RangeSelection,
): CaretRange<CaretDirection>;
declare export function $getAdjacentSiblingOrParentSiblingCaret<
  D: CaretDirection,
>(
  startCaret: NodeCaret<D>,
  rootMode?: RootMode
): null | [NodeCaret<D>, number]
declare export function $getCaretInDirection<
  Caret: PointCaret<CaretDirection>,
  D: CaretDirection,
>(
  caret: Caret,
  direction: D,
):
  | NodeCaret<D>
  | (Caret extends TextPointCaret<TextNode, CaretDirection>
      ? TextPointCaret<TextNode, D>
      : empty);
declare export function $getCaretRangeInDirection<D: CaretDirection>(
  range: CaretRange<CaretDirection>,
  direction: D,
): CaretRange<D>;
declare export function $getChildCaretAtIndex<D: CaretDirection>(
  parent: ElementNode,
  index: number,
  direction: D,
): NodeCaret<D>;
declare export function $normalizeCaret<D: CaretDirection>(
  initialCaret: PointCaret<D>,
): PointCaret<D>;
declare export function $removeTextFromCaretRange<D: CaretDirection>(
  initialRange: CaretRange<D>,
  sliceMode?:
    | 'removeEmptySlices'
    | 'preserveEmptyTextSliceCaret'
): CaretRange<D>;
declare export function $rewindSiblingCaret<
  T: LexicalNode,
  D: CaretDirection,
>(caret: SiblingCaret<T, D>): NodeCaret<D>;
declare export function $setPointFromCaret<D: CaretDirection>(
  point: PointType,
  caret: PointCaret<D>,
): void;
declare export function $setSelectionFromCaretRange(
  caretRange: CaretRange<CaretDirection>,
): RangeSelection;
declare export function $updateRangeSelectionFromCaretRange(
  selection: RangeSelection,
  caretRange: CaretRange<CaretDirection>,
): void;

export type CommonAncestorResult<
  A: LexicalNode,
  B: LexicalNode,
> =
  | CommonAncestorResultSame<A>
  | CommonAncestorResultAncestor<A & ElementNode>
  | CommonAncestorResultDescendant<B & ElementNode>
  | CommonAncestorResultBranch<A, B>;
export interface CommonAncestorResultBranch<
  A: LexicalNode,
  B: LexicalNode,
> {
  +type: 'branch';
  +commonAncestor: ElementNode;
  +a: A | ElementNode;
  +b: B | ElementNode;
}
export interface CommonAncestorResultAncestor<A: ElementNode> {
  +type: 'ancestor';
  +commonAncestor: A;
}
export interface CommonAncestorResultDescendant<B: ElementNode> {
  +type: 'descendant';
  +commonAncestor: B;
}
export interface CommonAncestorResultSame<A: LexicalNode> {
  +type: 'same';
  +commonAncestor: A;
}
declare export function $comparePointCaretNext(
  a: PointCaret<'next'>,
  b: PointCaret<'next'>,
): -1 | 0 | 1;
declare export function $getCommonAncestorResultBranchOrder<
  A: LexicalNode,
  B: LexicalNode,
>(compare: CommonAncestorResultBranch<A, B>): -1 | 1 ;
declare export function $getCommonAncestor<
  A: LexicalNode,
  B: LexicalNode,
>(a: A, b: B): null | CommonAncestorResult<A, B>;
declare export function $extendCaretToRange<D: CaretDirection>(
  anchor: PointCaret<D>,
): CaretRange<D>;
declare export function $getCollapsedCaretRange<D: CaretDirection>(
  anchor: PointCaret<D>,
): CaretRange<D>;
declare export function $isExtendableTextPointCaret<D: CaretDirection>(
  caret: PointCaret<D>
): implies caret is TextPointCaret<TextNode, D>;

export interface SplitAtPointCaretNextOptions {
  $copyElementNode?: (node: ElementNode) => ElementNode;
  $splitTextPointCaretNext?: (
    caret: TextPointCaret<TextNode, 'next'>,
  ) => NodeCaret<'next'>;
  rootMode?: RootMode;
  $shouldSplit?: (node: ElementNode, edge: 'first' | 'last') => boolean;
}
declare export function $splitAtPointCaretNext(
  pointCaret: PointCaret<'next'>,
  options?: SplitAtPointCaretNextOptions,
): null | NodeCaret<'next'>;

/**
 * LexicalUpdateTags
 */
declare export var COLLABORATION_TAG: 'collaboration';
declare export var HISTORIC_TAG: 'historic';
declare export var HISTORY_MERGE_TAG: 'history-merge';
declare export var HISTORY_PUSH_TAG: 'history-push';
declare export var PASTE_TAG: 'paste';
declare export var SKIP_COLLAB_TAG: 'skip-collab';
declare export var SKIP_DOM_SELECTION_TAG: 'skip-dom-selection';
declare export var SKIP_SCROLL_INTO_VIEW_TAG: 'skip-scroll-into-view';
export type UpdateTag =
  typeof COLLABORATION_TAG
  | typeof HISTORIC_TAG
  | typeof HISTORY_MERGE_TAG
  | typeof HISTORY_PUSH_TAG
  | typeof PASTE_TAG
  | typeof SKIP_COLLAB_TAG
  | typeof SKIP_DOM_SELECTION_TAG
  | typeof SKIP_SCROLL_INTO_VIEW_TAG
  | string;

/**
 * LexicalNodeState
 */
export const NODE_STATE_KEY = '$';
export type ValueOrUpdater<V> = V | ((prevValue: V) => V);
export type StateConfigValue<S: AnyStateConfig> = S extends StateConfig<
  infer _K,
  infer V
>
  ? V
  : empty;
export type StateConfigKey<S: AnyStateConfig> = S extends StateConfig<
  infer K,
  infer _V
>
  ? K
  : empty;
export interface NodeStateConfig<S: AnyStateConfig> {
  stateConfig: S;
  flat?: boolean;
}

export type RequiredNodeStateConfig =
  | NodeStateConfig<AnyStateConfig>
  | AnyStateConfig;

export type StateConfigJSON<S> = S extends StateConfig<infer K, infer V>
  ? {[Key in K]?: V}
  : Record<empty, empty>;

export type RequiredNodeStateConfigJSON<
  Config: RequiredNodeStateConfig,
  Flat: boolean,
> = StateConfigJSON<
  Config extends NodeStateConfig<infer S>
    ? {flat: false, ...Config} extends {flat: Flat}
      ? S
      : empty
    : false extends Flat
    ? Config
    : empty
>;
export type StateValueOrUpdater<Cfg: AnyStateConfig> = ValueOrUpdater<
  StateConfigValue<Cfg>
>;
// $FlowFixMe[unclear-type]
export type AnyStateConfig = StateConfig<any, any>;
export type NodeStateJSON<T: LexicalNode> = Partial<{
    [typeof NODE_STATE_KEY]: CollectStateJSON<GetNodeStateConfig<T>, false>;
  }> & CollectStateJSON<GetNodeStateConfig<T>, true>;

declare export class StateConfig<K: string, V> {
  +key: K;
  +parse: (value?: mixed) => V;
  +unparse: (value: V) => mixed;
  +isEqual: (a: V, b: V) => boolean;
  +defaultValue: V;
  constructor(key: K, stateValueConfig: StateValueConfig<V>): this;
}

export type StateValueConfig<V> = {
  parse: (jsonValue: mixed) => V;
  unparse?: (parsed: V) => mixed;
  isEqual?: (a: V, b: V) => boolean;
}

export type UnionToIntersection<T> = (
  // $FlowFixMe[unclear-type]
  T extends mixed ? (x: T) => mixed : empty
  // $FlowFixMe[unclear-type]
) extends (x: infer R) => any
  ? R
  : empty;

export type CollectStateJSON<
  Tuple: $ReadOnlyArray<RequiredNodeStateConfig>,
  Flat: boolean,
> = UnionToIntersection<
  {[K in keyof Tuple]: RequiredNodeStateConfigJSON<Tuple[K], Flat>}[number]
>;

export const PROTOTYPE_CONFIG_METHOD = '$config';

export interface StaticNodeConfigValue<
  T: LexicalNode,
  Type: string,
> {
  +type?: Type;
  +$transform?: (node: T) => void;
  +$importJSON?: (serializedNode: SerializedLexicalNode) => T;
  +importDOM?: DOMConversionMap;
  +stateConfigs?: $ReadOnlyArray<RequiredNodeStateConfig>;
  +extends?: Class<LexicalNode>;
}

/**
 * This is the type of LexicalNode.$config() that can be
 * overridden by subclasses.
 */
export type BaseStaticNodeConfig = {
  +[K in string]?: StaticNodeConfigValue<LexicalNode, string>;
};

/**
 * Used to extract the node and type from a StaticNodeConfigRecord
 */
export type StaticNodeConfig<
  T: LexicalNode,
  Type: string,
> = BaseStaticNodeConfig & {
  +[K in Type]?: StaticNodeConfigValue<T, Type>;
};

// $FlowFixMe[unclear-type]
export type AnyStaticNodeConfigValue = StaticNodeConfigValue<any, any>;

export type StaticNodeConfigRecord<
  Type: string,
  Config: AnyStaticNodeConfigValue,
> = BaseStaticNodeConfig & {
  +[K in Type]?: Config;
};

type GetStaticNodeConfig<T: LexicalNode> = ReturnType<
  T[typeof PROTOTYPE_CONFIG_METHOD]
> extends infer Record
  ? Record extends StaticNodeConfigRecord<infer Type, infer Config>
    ? Config & {+type: Type}
    : empty
  : empty;
type GetStaticNodeConfigs<T: LexicalNode> =
  GetStaticNodeConfig<T> extends infer OwnConfig
    ? OwnConfig extends empty
      ? []
      : OwnConfig extends {extends: Class<infer Parent>}
      ? GetStaticNodeConfig<Parent> extends infer ParentNodeConfig
        ? ParentNodeConfig extends empty
          ? [OwnConfig]
          : [OwnConfig, ...GetStaticNodeConfigs<Parent>]
        : OwnConfig
      : [OwnConfig]
    : [];

type CollectStateConfigs<Configs> = Configs extends [
  infer OwnConfig,
  ...infer ParentConfigs,
]
  ? OwnConfig extends {stateConfigs: infer StateConfigs}
    ? StateConfigs extends $ReadOnlyArray<RequiredNodeStateConfig>
      ? [...StateConfigs, ...CollectStateConfigs<ParentConfigs>]
      : CollectStateConfigs<ParentConfigs>
    : CollectStateConfigs<ParentConfigs>
  : [];

export type GetNodeStateConfig<T: LexicalNode> = CollectStateConfigs<
  GetStaticNodeConfigs<T>
>;

declare export function $getState<K: string, V>(
  node: LexicalNode,
  stateConfig: StateConfig<K, V>,
  version?: 'latest' | 'direct',
): V;
declare export function $getStateChange<T: LexicalNode, K: string, V>(
  node: T,
  prevNode: T,
  stateConfig: StateConfig<K, V>,
): null | [value: V, prevValue: V];
declare export function $getWritableNodeState<T: LexicalNode>(
  node: T,
): NodeState<T>;
type KnownStateMap = Map<AnyStateConfig, mixed>;
type UnknownStateRecord = Record<string, mixed>;
type SharedConfigMap = Map<string, AnyStateConfig>;
export type SharedNodeState = {
  sharedConfigMap: SharedConfigMap;
  flatKeys: Set<string>;
};
declare export class NodeState<T: LexicalNode> {
  +node: LexicalNode;
  +knownState: KnownStateMap;
  unknownState: void | UnknownStateRecord;
  +sharedNodeState: SharedNodeState;
  size: number;
  constructor(
    node: T,
    sharedNodeState: SharedNodeState,
    unknownState?: void | UnknownStateRecord,
    knownState?: KnownStateMap,
    size?: number | void,
  ): this;
  getValue<K: string, V>(stateConfig: StateConfig<K, V>): V;
  getInternalState(): [
    {+[k in string]: mixed} | void,
    $ReadOnlyMap<AnyStateConfig, mixed>,
  ];
  toJSON(): NodeStateJSON<T>;
  getWritable(node: T): NodeState<T>;
  updateFromKnown<K: string, V>(
    stateConfig: StateConfig<K, V>,
    value: V,
  ): void;
  updateFromUnknown(k: string, v: mixed): void;
    updateFromJSON(unknownState: void | UnknownStateRecord): void;
}
declare export function $setState<Node: LexicalNode, K: string, V>(
  node: Node,
  stateConfig: StateConfig<K, V>,
  valueOrUpdater: ValueOrUpdater<V>,
): Node;
export type LexicalNodeConfig = Class<LexicalNode> | LexicalNodeReplacement;
declare export function createSharedNodeState(
  nodeConfig: LexicalNodeConfig,
): SharedNodeState;
declare export function createState<K: string, V>(
  key: K,
  valueConfig: StateValueConfig<V>,
): StateConfig<K, V>;
declare export function $create<T>(cls: Class<T>): T;
declare export function getStaticNodeConfig(cls: Class<LexicalNode>): {
  ownNodeType: void | string;
  ownNodeConfig: void | StaticNodeConfigValue<LexicalNode, string>;
};

declare export function $isEditorState(x: mixed): x is EditorState;

// $FlowFixMe[unclear-type]
export type AnyLexicalExtension = LexicalExtension<any, string, any, any>;
export type AnyLexicalExtensionArgument =
  | AnyLexicalExtension
  | AnyNormalizedLexicalExtensionArgument;
export type AnyNormalizedLexicalExtensionArgument =
  // $FlowFixMe[unclear-type]
  NormalizedLexicalExtensionArgument<any, string, any, any>;  
declare export function configExtension<
  Config: ExtensionConfigBase,
  Name: string,
  Output,
  Init,
>(
  ...args: NormalizedLexicalExtensionArgument<Config, Name, Output, Init>
): NormalizedLexicalExtensionArgument<Config, Name, Output, Init>;
declare export const configTypeSymbol: symbol;
declare export function declarePeerDependency<
  Extension: AnyLexicalExtension = empty,
>(
  name: Extension['name'],
  config?: Partial<LexicalExtensionConfig<Extension>>,
): NormalizedPeerDependency<Extension>;
declare export function defineExtension<
  Config: ExtensionConfigBase,
  Name: string,
  Output,
  Init,
>(
  extension: LexicalExtension<Config, Name, Output, Init>,
): LexicalExtension<Config, Name, Output, Init>;
export type ExtensionConfigBase = {...};
export interface ExtensionInitState {
  getPeer: <Dependency: AnyLexicalExtension = empty>(
    name: Dependency['name'],
  ) => void | Omit<LexicalExtensionDependency<Dependency>, 'output'>;
  getDependency: <Dependency: AnyLexicalExtension>(
    dep: Dependency,
  ) => Omit<LexicalExtensionDependency<Dependency>, 'output'>;
  getDirectDependentNames: () => $ReadOnlySet<string>;
  getPeerNameSet: () => $ReadOnlySet<string>;
}
export interface ExtensionBuildState<Init>
  extends Omit<ExtensionInitState, 'getPeer' | 'getDependency'> {
  getPeer: <Dependency: AnyLexicalExtension = empty>(
    name: Dependency['name'],
  ) => void | LexicalExtensionDependency<Dependency>;
  getDependency: <Dependency: AnyLexicalExtension>(
    dep: Dependency,
  ) => LexicalExtensionDependency<Dependency>;
  getInitResult: () => Init;
}

export interface ExtensionRegisterState<Init, Output>
  extends ExtensionBuildState<Init> {
  getSignal: () => AbortSignal;
  getOutput: () => Output;
}
export interface InitialEditorConfig {
  disableEvents?: CreateEditorArgs['disableEvents'];
  parentEditor?: CreateEditorArgs['parentEditor'];
  namespace?: CreateEditorArgs['namespace'];
  nodes?: CreateEditorArgs['nodes'];
  theme?: CreateEditorArgs['theme'];
  html?: CreateEditorArgs['html'];
  editable?: CreateEditorArgs['editable'];
  onError?: (error: Error, editor: LexicalEditor) => void;
  $initialEditorState?: InitialEditorStateType;
}
export type InitialEditorStateType =
  | null
  | string
  | EditorState
  | ((editor: LexicalEditor) => void);
declare export const initTypeSymbol: symbol;

interface Disposable {
  // [Symbol.dispose]: () => void;
}
export interface LexicalEditorWithDispose extends LexicalEditor, Disposable {
  dispose: () => void;
}
export interface LexicalExtension<
  Config: ExtensionConfigBase,
  Name: string,
  Output,
  Init,
> extends InitialEditorConfig,
    LexicalExtensionInternal<Config, Output, Init> {
  +name: Name;
  conflictsWith?: string[];
  dependencies?: AnyLexicalExtensionArgument[];
  peerDependencies?: NormalizedPeerDependency<AnyLexicalExtension>[];
  config?: Config;
  mergeConfig?: (config: Config, overrides: Partial<Config>) => Config;
  init?: (
    editorConfig: InitialEditorConfig,
    config: Config,
    state: ExtensionInitState,
  ) => Init;
  build?: (
    editor: LexicalEditor,
    config: Config,
    state: ExtensionBuildState<Init>,
  ) => Output;
  register?: (
    editor: LexicalEditor,
    config: Config,
    state: ExtensionRegisterState<Init, Output>,
  ) => () => void;
  afterRegistration?: (
    editor: LexicalEditor,
    config: Config,
    state: ExtensionRegisterState<Init, Output>,
  ) => () => void;
}
export type LexicalExtensionArgument<
  Config: ExtensionConfigBase,
  Name: string,
  Output,
  Init,
> =
  | LexicalExtension<Config, Name, Output, Init>
  | NormalizedLexicalExtensionArgument<Config, Name, Output, Init>;
export type LexicalExtensionConfig<+Extension: AnyLexicalExtension> =
  $NonMaybeType<Extension[typeof configTypeSymbol]>;
export interface LexicalExtensionDependency<
  +Dependency: AnyLexicalExtension,
> {
  +config: LexicalExtensionConfig<Dependency>;
  +output: LexicalExtensionOutput<Dependency>;
}
export type LexicalExtensionInit<Extension: AnyLexicalExtension> =
  $NonMaybeType<Extension[typeof initTypeSymbol]>;
export interface LexicalExtensionInternal<Config, Output, Init> extends LexicalExtensionInternalConfig<Config>, LexicalExtensionInternalOutput<Output> {
  [typeof initTypeSymbol]: Init;
}
interface LexicalExtensionInternalConfig<Config> {
  [typeof configTypeSymbol]: Config;
}
interface LexicalExtensionInternalOutput<Output> {
  [typeof outputTypeSymbol]: Output;
}

export type LexicalExtensionName<Extension: AnyLexicalExtension> =
  Extension['name'];

export type LexicalExtensionOutput<+Extension: AnyLexicalExtension> =
  $NonMaybeType<Extension[typeof outputTypeSymbol]>;
declare export const peerDependencySymbol: symbol;
export type NormalizedLexicalExtensionArgument<
  Config: ExtensionConfigBase,
  Name: string,
  Output,
  Init,
> = [LexicalExtension<Config, Name, Output, Init>, ...Partial<Config>[]];
export type NormalizedPeerDependency<Extension: AnyLexicalExtension> = [
  Extension['name'],
  Partial<LexicalExtensionConfig<Extension>> | void,
] & {+[typeof peerDependencySymbol]: Extension};
export type OutputComponentExtension<ComponentType> =
  // $FlowFixMe[unclear-type]
  LexicalExtension<any, any, {Component: ComponentType}, any>;
declare export const outputTypeSymbol: symbol;
declare export function safeCast<T>(value: T): T;
declare export function shallowMergeConfig<T: ExtensionConfigBase>(
  config: T,
  overrides?: Partial<T>,
): T;
declare export function getTransformSetFromKlass(
  klass: Class<typeof LexicalNode>,
): Set<Transform<LexicalNode>>;

declare export function addClassNamesToElement(
  element: HTMLElement,
  ...classNames: Array<typeof undefined | boolean | null | string>
): void;
declare export function removeClassNamesFromElement(
  element: HTMLElement,
  ...classNames: Array<typeof undefined | boolean | null | string>
): void;
declare export function mergeRegister(...func: Array<() => void>): () => void;
declare export function normalizeClassNames(...classNames: Array<typeof undefined | boolean | null | string>): Array<string>;
