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

import type {
  AnyLexicalExtension,
  BaseSelection,
  DOMExportOutput,
  EditorDOMRenderConfig,
  DOMSlotForNode,
  LexicalEditor,
  LexicalExtension,
  LexicalNode,
  StateConfig,
} from 'lexical';

// ContextRecord types

type AnyContextSymbol = symbol;

type ContextRecord<K extends symbol> = {readonly [string | symbol]: unknown};

type ContextConfig<Sym extends symbol, V> = StateConfig<symbol, V>;

type ContextConfigUpdater<Ctx extends AnyContextSymbol, V> = {
  readonly cfg: ContextConfig<Ctx, V>,
  readonly updater: (prev: V) => V,
};

type ContextConfigPair<Ctx extends AnyContextSymbol, V> = Readonly<
  [ContextConfig<Ctx, V>, V],
>;

export type ContextPairOrUpdater<Ctx extends AnyContextSymbol, V> =
  | ContextConfigPair<Ctx, V>
  | ContextConfigUpdater<Ctx, V>;

// $FlowFixMe[unclear-type]
type AnyContextConfigPairOrUpdater<Ctx extends AnyContextSymbol> =
  // $FlowFixMe[unclear-type]
  ContextPairOrUpdater<Ctx, any>;

// Render types

export type RenderStateConfig<V> = ContextConfig<symbol, V>;

// $FlowFixMe[unclear-type]
export type AnyRenderStateConfigPairOrUpdater =
  // $FlowFixMe[unclear-type]
  AnyContextConfigPairOrUpdater<any>;

// $FlowFixMe[unclear-type]
export type AnyRenderStateConfig = RenderStateConfig<any>;

export type DOMRenderExtensionOutput = {
  defaults: void | ContextRecord<symbol>,
};

export type DOMRenderConfig = {
  overrides: AnyDOMRenderMatch[],
  contextDefaults: AnyRenderStateConfigPairOrUpdater[],
};

// $FlowFixMe[unclear-type]
export type AnyDOMRenderMatch = DOMRenderMatch<any>;

export type NodeMatch<T extends LexicalNode> =
  | Class<T>
  | ((node: LexicalNode) => boolean);

export type DOMRenderMatch<T extends LexicalNode> = {
  readonly nodes: '*' | ReadonlyArray<NodeMatch<T>>,
  readonly $getDOMSlot?: (
    node: T,
    dom: HTMLElement,
    $next: () => DOMSlotForNode<T>,
    editor: LexicalEditor,
  ) => DOMSlotForNode<T>,
  readonly $getSlotTargetElement?: (
    node: T,
    slotName: string,
    hostDom: HTMLElement,
    $next: () => HTMLElement | null,
    editor: LexicalEditor,
  ) => HTMLElement | null,
  readonly $createDOM?: (
    node: T,
    $next: () => HTMLElement,
    editor: LexicalEditor,
  ) => HTMLElement,
  readonly $updateDOM?: (
    nextNode: T,
    prevNode: T,
    dom: HTMLElement,
    $next: () => boolean,
    editor: LexicalEditor,
  ) => boolean,
  readonly $decorateDOM?: (
    nextNode: T,
    prevNode: T | null,
    dom: HTMLElement,
    editor: LexicalEditor,
  ) => void,
  readonly $exportDOM?: (
    node: T,
    $next: () => DOMExportOutput,
    editor: LexicalEditor,
  ) => DOMExportOutput,
  readonly $shouldExclude?: (
    node: T,
    selection: null | BaseSelection,
    $next: () => boolean,
    editor: LexicalEditor,
  ) => boolean,
  readonly $shouldInclude?: (
    node: T,
    selection: null | BaseSelection,
    $next: () => boolean,
    editor: LexicalEditor,
  ) => boolean,
  readonly $extractWithChild?: (
    node: T,
    childNode: LexicalNode,
    selection: null | BaseSelection,
    destination: 'clone' | 'html',
    $next: () => boolean,
    editor: LexicalEditor,
  ) => boolean,
};

// contextUpdater, contextValue

declare export function contextUpdater<Ctx extends AnyContextSymbol, V>(
  cfg: ContextConfig<Ctx, V>,
  updater: (prev: V) => V,
): ContextConfigUpdater<Ctx, V>;

declare export function contextValue<Ctx extends AnyContextSymbol, V>(
  cfg: ContextConfig<Ctx, V>,
  value: V,
): ContextConfigPair<Ctx, V>;

// domOverride

declare export function domOverride(
  nodes: '*',
  config: Omit<DOMRenderMatch<LexicalNode>, 'nodes'>,
): DOMRenderMatch<LexicalNode>;
declare export function domOverride<T extends LexicalNode>(
  nodes: ReadonlyArray<NodeMatch<T>>,
  config: Omit<DOMRenderMatch<T>, 'nodes'>,
): DOMRenderMatch<T>;

// DOMRenderExtension

declare export var DOMRenderExtension: LexicalExtension<
  DOMRenderConfig,
  '@lexical/html/DOM',
  DOMRenderExtensionOutput,
  void,
>;

// RenderContext

declare export var RenderContextExport: RenderStateConfig<boolean>;
declare export var RenderContextRoot: RenderStateConfig<boolean>;

declare export function $getRenderContextValue<V>(
  cfg: RenderStateConfig<V>,
  editor?: LexicalEditor,
): V;

declare export function $withRenderContext(
  cfg: ReadonlyArray<AnyRenderStateConfigPairOrUpdater>,
  editor?: LexicalEditor,
): <T>(f: () => T) => T;

// Existing exports

declare export function $generateNodesFromDOM(
  editor: LexicalEditor,
  dom: Document,
): LexicalNode[];

declare export function $generateDOMFromNodes<
  T extends HTMLElement | DocumentFragment,
>(
  container: T,
  selection?: null | BaseSelection,
  editor?: LexicalEditor,
): T;

declare export function $generateDOMFromRoot<T extends HTMLElement | DocumentFragment>(
  container: T, root?: LexicalNode,
): T;

declare export function $generateHtmlFromNodes(
  editor: LexicalEditor,
  selection?: BaseSelection | null,
): string;

declare export function $appendNodeToHTML(
  editor: LexicalEditor,
  node: LexicalNode,
  parentElement: HTMLElement | DocumentFragment,
  selection?: BaseSelection | null,
): boolean;

