/**
 * 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 {JSX} from 'react';

import {Signal, signal} from '@lexical/extension';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {
  $isScrollableTablesActive,
  registerTableCellUnmergeTransform,
  registerTablePlugin,
  registerTableSelectionObserver,
  setScrollableTablesActive,
  TableCellNode,
} from '@lexical/table';
import {$fullReconcile} from 'lexical';
import {useEffect, useState} from 'react';

/**
 * Props for the {@link TablePlugin} component.
 */
export interface TablePluginProps {
  /**
   * When `false` (default `true`), merged cell support (colspan and rowspan) will be disabled and all
   * tables will be forced into a regular grid with 1x1 table cells.
   */
  hasCellMerge?: boolean;
  /**
   * When `false` (default `true`), the background color of TableCellNode will always be removed.
   */
  hasCellBackgroundColor?: boolean;
  /**
   * When `true` (default `true`), the tab key can be used to navigate table cells.
   */
  hasTabHandler?: boolean;
  /**
   * When `true` (default `false`), tables will be wrapped in a `<div>` to enable horizontal scrolling
   */
  hasHorizontalScroll?: boolean;
  /**
   * When `true` (default `false`), nested tables will be allowed.
   *
   * @experimental Nested tables are not officially supported.
   */
  hasNestedTables?: boolean;
}

/**
 * A plugin to enable all of the features of Lexical's TableNode.
 *
 * @param props - See type for documentation
 * This is a legacy plugin. When building an editor with the extension API,
 * configure {@link TableExtension} instead.
 *
 * @returns An element to render in your LexicalComposer
 */
export function TablePlugin({
  hasCellMerge = true,
  hasCellBackgroundColor = true,
  hasTabHandler = true,
  hasHorizontalScroll = false,
  hasNestedTables = false,
}: TablePluginProps): JSX.Element | null {
  const [editor] = useLexicalComposerContext();

  useEffect(() => {
    const hadHorizontalScroll = $isScrollableTablesActive(editor);
    if (hadHorizontalScroll !== hasHorizontalScroll) {
      setScrollableTablesActive(editor, hasHorizontalScroll);
      // Re-render existing tables through the new scroll-wrapper config without
      // cloning every TableNode the way marking them dirty would. A full
      // reconcile marks no nodes dirty, so it's deferred (no synchronous render
      // from this effect) and produces no history entry.
      editor.update($fullReconcile);
    }
  }, [editor, hasHorizontalScroll]);

  const hasNestedTablesSignal = usePropSignal(hasNestedTables);

  useEffect(
    () => registerTablePlugin(editor, {hasNestedTables: hasNestedTablesSignal}),
    [editor, hasNestedTablesSignal],
  );

  useEffect(
    () => registerTableSelectionObserver(editor, hasTabHandler),
    [editor, hasTabHandler],
  );

  // Unmerge cells when the feature isn't enabled
  useEffect(() => {
    if (!hasCellMerge) {
      return registerTableCellUnmergeTransform(editor);
    }
  }, [editor, hasCellMerge]);

  // Remove cell background color when feature is disabled
  useEffect(() => {
    if (hasCellBackgroundColor) {
      return;
    }
    return editor.registerNodeTransform(TableCellNode, node => {
      if (node.getBackgroundColor() !== null) {
        node.setBackgroundColor(null);
      }
    });
  }, [editor, hasCellBackgroundColor, hasCellMerge]);

  return null;
}

function usePropSignal<T>(value: T): Signal<T> {
  const [configSignal] = useState(() => signal(value));
  if (configSignal.peek() !== value) {
    // eslint-disable-next-line react-hooks/immutability
    configSignal.value = value;
  }
  return configSignal;
}
