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

import type {EditorThemeClasses, LexicalEditor} from 'lexical';

import invariant from '@lexical/internal/invariant';
import {createContext as createReactContext, useContext} from 'react';

/**
 * The context value provided alongside a {@link LexicalEditor} by a
 * {@link LexicalComposer}. It exposes a `getTheme()` function that resolves the
 * active {@link EditorThemeClasses}, falling back to any parent composer's
 * theme.
 */
export type LexicalComposerContextType = {
  getTheme: () => EditorThemeClasses | null | undefined;
};

/**
 * A tuple of the {@link LexicalEditor} and its
 * {@link LexicalComposerContextType}, as stored in {@link LexicalComposerContext}
 * and returned by {@link useLexicalComposerContext}.
 */
export type LexicalComposerContextWithEditor = [
  LexicalEditor,
  LexicalComposerContextType,
];

/**
 * The React context used to share the {@link LexicalEditor} and its
 * {@link LexicalComposerContextType} with descendant plugins and components.
 * Most code should read it through {@link useLexicalComposerContext} rather than
 * consuming the context directly.
 */
export const LexicalComposerContext: React.Context<
  LexicalComposerContextWithEditor | null | undefined
> = createReactContext<LexicalComposerContextWithEditor | null | undefined>(
  null,
);

/**
 * Creates a {@link LexicalComposerContextType} for a composer. Theme resolution
 * falls back to the optional `parent` context, so nested composers inherit the
 * parent's theme unless they provide their own.
 *
 * @param parent - The parent composer context to inherit from, if any.
 * @param theme - The theme classes for this composer, or `null`/`undefined` to
 * inherit from `parent`.
 * @returns The new composer context value.
 */
export function createLexicalComposerContext(
  parent: LexicalComposerContextWithEditor | null | undefined,
  theme: EditorThemeClasses | null | undefined,
): LexicalComposerContextType {
  let parentContext: LexicalComposerContextType | null = null;

  if (parent != null) {
    parentContext = parent[1];
  }

  function getTheme() {
    if (theme != null) {
      return theme;
    }

    return parentContext != null ? parentContext.getTheme() : null;
  }

  return {
    getTheme,
  };
}

/**
 * Returns the {@link LexicalEditor} and its {@link LexicalComposerContextType}
 * from the nearest {@link LexicalComposer} (or nested composer). This is the
 * primary way plugins and components access the editor instance.
 *
 * @returns The `[editor, context]` tuple for the current composer.
 * @throws If called outside of a LexicalComposer.
 */
export function useLexicalComposerContext(): LexicalComposerContextWithEditor {
  const composerContext = useContext(LexicalComposerContext);

  if (composerContext == null) {
    invariant(
      false,
      'LexicalComposerContext.useLexicalComposerContext: cannot find a LexicalComposerContext',
    );
  }

  return composerContext;
}
