/*!
 * VisualEditor ContentEditable table arrow key down handler
 *
 * @copyright See AUTHORS.txt
 */

/* istanbul ignore next */
/**
 * Arrow key down handler for table selections.
 *
 * @class
 * @extends ve.ce.KeyDownHandler
 *
 * @constructor
 */
ve.ce.TableArrowKeyDownHandler = function VeCeTableArrowKeyDownHandler() {
	// Parent constructor - never called because class is fully static
	// ve.ui.TableArrowKeyDownHandler.super.apply( this, arguments );
};

/* Inheritance */

OO.inheritClass( ve.ce.TableArrowKeyDownHandler, ve.ce.KeyDownHandler );

/* Static properties */

ve.ce.TableArrowKeyDownHandler.static.name = 'tableArrow';

ve.ce.TableArrowKeyDownHandler.static.keys = [
	OO.ui.Keys.UP, OO.ui.Keys.DOWN, OO.ui.Keys.LEFT, OO.ui.Keys.RIGHT,
	OO.ui.Keys.HOME, OO.ui.Keys.END, OO.ui.Keys.PAGEUP, OO.ui.Keys.PAGEDOWN,
	OO.ui.Keys.TAB
];

ve.ce.TableArrowKeyDownHandler.static.supportedSelections = [ 'table' ];

/* Static methods */

/**
 * @inheritdoc
 */
ve.ce.TableArrowKeyDownHandler.static.execute = function ( surface, e ) {
	let wrap = false,
		checkDir = false,
		colOffset = 0,
		rowOffset = 0,
		expand = e.shiftKey;

	if ( e.ctrlKey || e.altKey || e.metaKey ) {
		// Support: Firefox
		// In Firefox, ctrl-tab to switch browser-tabs still triggers the
		// keydown event.
		return;
	}

	switch ( e.keyCode ) {
		case OO.ui.Keys.LEFT:
			colOffset = -1;
			checkDir = true;
			break;
		case OO.ui.Keys.RIGHT:
			colOffset = 1;
			checkDir = true;
			break;
		case OO.ui.Keys.UP:
			rowOffset = -1;
			break;
		case OO.ui.Keys.DOWN:
			rowOffset = 1;
			break;
		case OO.ui.Keys.HOME:
			colOffset = -Infinity;
			break;
		case OO.ui.Keys.END:
			colOffset = Infinity;
			break;
		case OO.ui.Keys.PAGEUP:
			rowOffset = -Infinity;
			break;
		case OO.ui.Keys.PAGEDOWN:
			rowOffset = Infinity;
			break;
		case OO.ui.Keys.TAB:
			colOffset = e.shiftKey ? -1 : 1;
			expand = false; // Shift-tab is a movement, not an expansion
			wrap = true;
			break;
	}

	e.preventDefault();

	this.moveTableSelection( surface, rowOffset, colOffset, checkDir, expand, wrap );
	return true;
};

/**
 * @param {ve.ce.Surface} surface
 * @param {number} rowOffset how many rows to move
 * @param {number} colOffset how many columns to move
 * @param {boolean} [checkDir=false] whether to translate offsets according to ltr settings
 * @param {boolean} [expand=false] whether to expand the selection or replace it
 * @param {boolean} [wrap=false] Wrap to the next/previous row at edges, insert new row at end
 */
ve.ce.TableArrowKeyDownHandler.static.moveTableSelection = function ( surface, rowOffset, colOffset, checkDir, expand, wrap ) {
	let selection = surface.getModel().getSelection();
	if ( colOffset && checkDir ) {
		const tableNode = surface.documentView.getBranchNodeFromOffset( selection.tableRange.start + 1 );
		if ( tableNode.$element.css( 'direction' ) !== 'ltr' ) {
			colOffset *= -1;
		}
	}
	if ( !expand ) {
		selection = selection.collapseToFrom();
	}

	let newSelection;

	function adjust() {
		newSelection = selection.newFromAdjustment(
			surface.getModel().getDocument(),
			expand ? 0 : colOffset,
			expand ? 0 : rowOffset,
			colOffset,
			rowOffset,
			wrap
		);
	}

	adjust();

	// If wrapping forwards didn't move, we must be at the end of the table,
	// so insert a new row and try again
	if ( wrap && colOffset > 0 && selection.equals( newSelection ) ) {
		surface.getSurface().execute( 'table', 'insert', 'row', 'after' );
		selection = surface.getModel().getSelection();
		adjust();
	}

	// If moving up/down didn't move, we must be at the start/end of the table,
	// so move outside
	if ( ( rowOffset !== 0 || ( rowOffset === 0 && colOffset === -1 && wrap ) ) && selection.equals( newSelection ) ) {
		const documentModel = surface.getModel().getDocument();
		let captionNode;
		if ( ( rowOffset === -1 || ( colOffset === -1 && wrap ) ) && ( captionNode = selection.getTableNode( documentModel ).getCaptionNode() ) ) {
			// If we're moving up/backwards, and there's a caption node, put the selection in it
			newSelection = new ve.dm.LinearSelection( documentModel.getRelativeRange( new ve.Range( captionNode.getRange().start ), 1 ) );
		} else {
			// Otherwise, go outside the table
			newSelection = new ve.dm.LinearSelection( documentModel.getRelativeRange( selection.tableRange, rowOffset || colOffset ) );
		}
	}

	surface.getModel().setSelection( newSelection );
};

/* Registration */

ve.ce.keyDownHandlerFactory.register( ve.ce.TableArrowKeyDownHandler );
