/**
 * 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 { $insertDataTransferForPlainText, $handlePlainTextDrop, $writeDragSourceToDataTransfer, $getHtmlContent } from '@lexical/clipboard';
import { DragonExtension } from '@lexical/dragon';
import { NormalizeInlineElementsExtension, NormalizeTripleClickSelectionExtension } from '@lexical/extension';
import { $shouldOverrideDefaultCharacterSelection, $moveCharacter } from '@lexical/selection';
import { objectKlassEquals } from '@lexical/utils';
import { mergeRegister, DELETE_CHARACTER_COMMAND, $getSelection, $isRangeSelection, COMMAND_PRIORITY_EDITOR, DELETE_WORD_COMMAND, DELETE_LINE_COMMAND, CONTROLLED_TEXT_INSERTION_COMMAND, REMOVE_TEXT_COMMAND, INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_BACKSPACE_COMMAND, IS_IOS, CAN_USE_BEFORE_INPUT, KEY_DELETE_COMMAND, KEY_ENTER_COMMAND, IS_SAFARI, IS_APPLE_WEBKIT, SELECT_ALL_COMMAND, $selectAll, $getSlotFrame, COPY_COMMAND, CUT_COMMAND, PASTE_COMMAND, DROP_COMMAND, DRAGSTART_COMMAND, defineExtension, CUT_TAG, PASTE_TAG } from '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.
 *
 */

function onCopyForPlainText(event, editor) {
  editor.update(() => {
    if (event !== null) {
      const clipboardData = objectKlassEquals(event, KeyboardEvent) ? null : event.clipboardData;
      const selection = $getSelection();
      if (selection !== null && !selection.isCollapsed() && clipboardData != null) {
        event.preventDefault();
        const htmlString = $getHtmlContent(editor);
        if (htmlString !== null) {
          clipboardData.setData('text/html', htmlString);
        }
        clipboardData.setData('text/plain', selection.getTextContent());
      }
    }
  });
}
function onPasteForPlainText(event, editor) {
  event.preventDefault();
  editor.update(() => {
    const selection = $getSelection();
    const clipboardData = objectKlassEquals(event, ClipboardEvent) ? event.clipboardData : null;
    if (clipboardData != null && $isRangeSelection(selection)) {
      $insertDataTransferForPlainText(clipboardData, selection);
    }
  }, {
    // PASTE_TAG gives the paste its own undo entry: @lexical/history treats
    // the tag as a history boundary so undoing a paste does not also undo any
    // typing that preceded it (see #8609).
    tag: PASTE_TAG
  });
}
function onCutForPlainText(event, editor) {
  onCopyForPlainText(event, editor);
  editor.update(() => {
    const selection = $getSelection();
    if ($isRangeSelection(selection)) {
      selection.removeText();
    }
  }, {
    // CUT_TAG gives the cut its own undo entry: @lexical/history treats the
    // tag as a history boundary so undoing a cut does not also undo any typing
    // that preceded it (see #8609).
    tag: CUT_TAG
  });
}
function registerPlainText(editor) {
  const removeListener = mergeRegister(editor.registerCommand(DELETE_CHARACTER_COMMAND, isBackward => {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return false;
    }
    selection.deleteCharacter(isBackward);
    return true;
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(DELETE_WORD_COMMAND, isBackward => {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return false;
    }
    selection.deleteWord(isBackward);
    return true;
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(DELETE_LINE_COMMAND, isBackward => {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return false;
    }
    selection.deleteLine(isBackward);
    return true;
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(CONTROLLED_TEXT_INSERTION_COMMAND, eventOrText => {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return false;
    }
    if (typeof eventOrText === 'string') {
      selection.insertText(eventOrText);
    } else {
      const dataTransfer = eventOrText.dataTransfer;
      if (dataTransfer != null) {
        $insertDataTransferForPlainText(dataTransfer, selection);
      } else {
        const data = eventOrText.data;
        if (data) {
          selection.insertText(data);
        }
      }
    }
    return true;
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(REMOVE_TEXT_COMMAND, () => {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return false;
    }
    selection.removeText();
    return true;
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(INSERT_LINE_BREAK_COMMAND, selectStart => {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return false;
    }
    selection.insertLineBreak(selectStart);
    return true;
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(INSERT_PARAGRAPH_COMMAND, () => {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return false;
    }
    selection.insertLineBreak();
    return true;
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(KEY_ARROW_LEFT_COMMAND, payload => {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return false;
    }
    const event = payload;
    const isHoldingShift = event.shiftKey;
    if ($shouldOverrideDefaultCharacterSelection(selection, true)) {
      event.preventDefault();
      $moveCharacter(selection, isHoldingShift, true);
      return true;
    }
    return false;
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(KEY_ARROW_RIGHT_COMMAND, payload => {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return false;
    }
    const event = payload;
    const isHoldingShift = event.shiftKey;
    if ($shouldOverrideDefaultCharacterSelection(selection, false)) {
      event.preventDefault();
      $moveCharacter(selection, isHoldingShift, false);
      return true;
    }
    return false;
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(KEY_BACKSPACE_COMMAND, event => {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return false;
    }

    // On iOS, blocking the keydown event's default prevents the system
    // keyboard from updating its autocomplete/autocorrect suggestion bar
    // after Backspace. Returning false here skips event.preventDefault()
    // on keydown; the beforeinput deleteContentBackward handler still runs
    // and performs the deletion, so editing behavior is unchanged.
    // See https://github.com/facebook/lexical/issues/5841
    if (IS_IOS && CAN_USE_BEFORE_INPUT) {
      return false;
    }
    event.preventDefault();
    return editor.dispatchCommand(DELETE_CHARACTER_COMMAND, true);
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(KEY_DELETE_COMMAND, event => {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return false;
    }
    event.preventDefault();
    return editor.dispatchCommand(DELETE_CHARACTER_COMMAND, false);
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(KEY_ENTER_COMMAND, event => {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return false;
    }
    if (event !== null) {
      // If we have beforeinput, then we can avoid blocking
      // the default behavior. This ensures that the iOS can
      // intercept that we're actually inserting a paragraph,
      // and autocomplete, autocapitalize etc work as intended.
      // This can also cause a strange performance issue in
      // Safari, where there is a noticeable pause due to
      // preventing the key down of enter.
      if ((IS_IOS || IS_SAFARI || IS_APPLE_WEBKIT) && CAN_USE_BEFORE_INPUT) {
        return false;
      }
      event.preventDefault();
    }
    return editor.dispatchCommand(INSERT_LINE_BREAK_COMMAND, false);
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(SELECT_ALL_COMMAND, () => {
    // Scope SELECT_ALL only when the caret is inside a named-slot frame:
    // slots are shadow-root isolated, so a whole-document select-all
    // would escape the slot and let a single keystroke replace the host.
    // Every other context (including TableCell shadow roots) keeps the
    // legacy whole-document behavior; block/document scoping elsewhere
    // is provided by the opt-in SelectBlockExtension.
    const selection = $getSelection();
    $selectAll($isRangeSelection(selection) && $getSlotFrame(selection.anchor.getNode()) !== null ? selection : null);
    return true;
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(COPY_COMMAND, event => {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return false;
    }
    onCopyForPlainText(event, editor);
    return true;
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(CUT_COMMAND, event => {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return false;
    }
    onCutForPlainText(event, editor);
    return true;
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(PASTE_COMMAND, event => {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return false;
    }
    onPasteForPlainText(event, editor);
    return true;
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(DROP_COMMAND, event => $handlePlainTextDrop(event, editor), COMMAND_PRIORITY_EDITOR), editor.registerCommand(DRAGSTART_COMMAND, event => {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return false;
    }
    // Mark the drag source so a drop in a different editor can remove
    // the source range to produce cut-and-paste semantics.
    if (!selection.isCollapsed() && event.dataTransfer !== null) {
      $writeDragSourceToDataTransfer(event.dataTransfer, editor);
    }
    return true;
  }, COMMAND_PRIORITY_EDITOR));
  return removeListener;
}

/**
 * An extension to register \@lexical/plain-text behavior
 */
const PlainTextExtension = /* @__PURE__ */defineExtension({
  conflictsWith: ['@lexical/rich-text'],
  dependencies: [DragonExtension, NormalizeInlineElementsExtension, NormalizeTripleClickSelectionExtension],
  name: '@lexical/plain-text',
  register: registerPlainText
});

export { PlainTextExtension, registerPlainText };
