/**
 * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
 */

/**
 * @module typing/deleteobserver
 */

import Observer from '@ckeditor/ckeditor5-engine/src/view/observer/observer';
import DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata';
import BubblingEventInfo from '@ckeditor/ckeditor5-engine/src/view/observer/bubblingeventinfo';
import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
import env from '@ckeditor/ckeditor5-utils/src/env';
import { isShiftDeleteOnNonCollapsedSelection } from './utils/utils';

/**
 * Delete observer introduces the {@link module:engine/view/document~Document#event:delete} event.
 *
 * @extends module:engine/view/observer/observer~Observer
 */
export default class DeleteObserver extends Observer {
	/**
	 * @inheritDoc
	 */
	constructor( view ) {
		super( view );

		const document = view.document;
		let sequence = 0;

		document.on( 'keyup', ( evt, data ) => {
			if ( data.keyCode == keyCodes.delete || data.keyCode == keyCodes.backspace ) {
				sequence = 0;
			}
		} );

		document.on( 'keydown', ( evt, data ) => {
			// Do not fire the `delete` event, if Shift + Delete key combination was pressed on a non-collapsed selection on Windows.
			//
			// The Shift + Delete key combination should work in the same way as the `cut` event on a non-collapsed selection on Windows.
			// In fact, the native `cut` event is actually emitted in this case, but with lower priority. Therefore, in order to handle the
			// Shift + Delete key combination correctly, it is enough not to emit the `delete` event.
			if ( env.isWindows && isShiftDeleteOnNonCollapsedSelection( data, document ) ) {
				return;
			}

			const deleteData = {};

			if ( data.keyCode == keyCodes.delete ) {
				deleteData.direction = 'forward';
				deleteData.unit = 'character';
			} else if ( data.keyCode == keyCodes.backspace ) {
				deleteData.direction = 'backward';
				deleteData.unit = 'codePoint';
			} else {
				return;
			}

			const hasWordModifier = env.isMac ? data.altKey : data.ctrlKey;
			deleteData.unit = hasWordModifier ? 'word' : deleteData.unit;
			deleteData.sequence = ++sequence;

			fireViewDeleteEvent( evt, data.domEvent, deleteData );
		} );

		// `beforeinput` is handled only for Android devices. Desktop Chrome and iOS are skipped because they are working fine now.
		if ( env.isAndroid ) {
			document.on( 'beforeinput', ( evt, data ) => {
				// If event type is other than `deleteContentBackward` then this is not deleting.
				if ( data.domEvent.inputType != 'deleteContentBackward' ) {
					return;
				}

				const deleteData = {
					unit: 'codepoint',
					direction: 'backward',
					sequence: 1
				};

				// Android IMEs may change the DOM selection on `beforeinput` event so that the selection contains all the text
				// that the IME wants to remove. We will pass this information to `delete` event so proper part of the content is removed.
				//
				// Sometimes it is only expanding by a one character (in case of collapsed selection). In this case we don't need to
				// set a different selection to remove, it will work just fine.
				const domSelection = data.domTarget.ownerDocument.defaultView.getSelection();

				if ( domSelection.anchorNode == domSelection.focusNode && domSelection.anchorOffset + 1 != domSelection.focusOffset ) {
					deleteData.selectionToRemove = view.domConverter.domSelectionToView( domSelection );
				}

				fireViewDeleteEvent( evt, data.domEvent, deleteData );
			} );
		}

		function fireViewDeleteEvent( originalEvent, domEvent, deleteData ) {
			const event = new BubblingEventInfo( document, 'delete', document.selection.getFirstRange() );

			document.fire( event, new DomEventData( document, domEvent, deleteData ) );

			// Stop the original event if `delete` event was stopped.
			// https://github.com/ckeditor/ckeditor5/issues/753
			if ( event.stop.called ) {
				originalEvent.stop();
			}
		}
	}

	/**
	 * @inheritDoc
	 */
	observe() {}
}

/**
 * Event fired when the user tries to delete content (e.g. presses <kbd>Delete</kbd> or <kbd>Backspace</kbd>).
 *
 * Note: This event is fired by the {@link module:typing/deleteobserver~DeleteObserver observer}
 * (usually registered by the {@link module:typing/delete~Delete delete feature}).
 *
 * @event module:engine/view/document~Document#event:delete
 * @param {module:engine/view/observer/domeventdata~DomEventData} data
 * @param {'forward'|'delete'} data.direction The direction in which the deletion should happen.
 * @param {'character'|'codePoint'|'word'} data.unit The "amount" of content that should be deleted.
 * @param {Number} data.sequence A number describing which subsequent delete event it is without the key being released.
 * If it's 2 or more it means that the key was pressed and hold.
 * @param {module:engine/view/selection~Selection} [data.selectionToRemove] View selection which content should be removed. If not set,
 * current selection should be used.
 */
