/*!
 * VisualEditor UserInterface BlockquoteAction class.
 *
 * @copyright See AUTHORS.txt
 */

/**
 * Blockquote action.
 *
 * @class
 * @extends ve.ui.Action
 * @constructor
 * @param {ve.ui.Surface} surface Surface to act on
 * @param {string} [source]
 */
ve.ui.BlockquoteAction = function VeUiBlockquoteAction() {
	// Parent constructor
	ve.ui.BlockquoteAction.super.apply( this, arguments );
};

/* Inheritance */

OO.inheritClass( ve.ui.BlockquoteAction, ve.ui.Action );

/* Static Properties */

ve.ui.BlockquoteAction.static.name = 'blockquote';

ve.ui.BlockquoteAction.static.methods = [ 'wrap', 'unwrap', 'toggle' ];

/* Methods */

/**
 * Check if the current selection is wrapped in a blockquote.
 *
 * @return {boolean} Current selection is wrapped in a blockquote
 */
ve.ui.BlockquoteAction.prototype.isWrapped = function () {
	const fragment = this.surface.getModel().getFragment();
	return fragment.hasMatchingAncestor( 'blockquote' );
};

/**
 * Toggle a blockquote around content.
 *
 * @return {boolean} Action was executed
 */
ve.ui.BlockquoteAction.prototype.toggle = function () {
	return this[ this.isWrapped() ? 'unwrap' : 'wrap' ]();
};

/**
 * Add a blockquote around content (only if it has no blockquote already).
 *
 * @return {boolean} Action was executed
 */
ve.ui.BlockquoteAction.prototype.wrap = function () {
	const surfaceModel = this.surface.getModel(),
		selection = surfaceModel.getSelection();

	if ( !( selection instanceof ve.dm.LinearSelection ) ) {
		return false;
	}

	let fragment = surfaceModel.getFragment( null, true );
	// Trim the selection range to the range of leaf nodes in the selection,
	// to avoid covering whole nodes where only start/end tag was selected.
	// For example:
	//     <p>asdf</p><p>qwer</p>   -->   <p>asdf</p><p>qwer</p>
	//        ^^^^^^^^^^^                    ^^^^
	const leaves = fragment.getSelectedLeafNodes();
	const leavesRange = new ve.Range(
		leaves[ 0 ].getRange().start,
		leaves[ leaves.length - 1 ].getRange().end
	);
	fragment = surfaceModel.getLinearFragment( leavesRange, true );

	// Expand to cover entire nodes
	fragment = fragment.expandLinearSelection( 'siblings' );

	// If the nodes can't be wrapped (e.g. they are list items), wrap the parent
	while (
		fragment.getCoveredNodes().some( ( nodeInfo ) => !nodeInfo.node.isAllowedParentNodeType( 'blockquote' ) || nodeInfo.node.isContent() )
	) {
		fragment = fragment.expandLinearSelection( 'parent' );
	}

	// Wrap everything in a blockquote
	fragment.wrapAllNodes( { type: 'blockquote' } );

	return true;
};

/**
 * Remove blockquote around content (if present).
 *
 * @return {boolean} Action was executed
 */
ve.ui.BlockquoteAction.prototype.unwrap = function () {
	const surfaceModel = this.surface.getModel(),
		selection = surfaceModel.getSelection();

	if ( !( selection instanceof ve.dm.LinearSelection ) ) {
		return false;
	}

	if ( !this.isWrapped() ) {
		return false;
	}

	let fragment = surfaceModel.getFragment( null, true );
	// Trim the selection range to the range of leaf nodes in the selection,
	// to avoid covering whole nodes where only start/end tag was selected.
	// For example:
	//     <bq><p>asdf</p></bq><p>qwer</p>   -->   <bq><p>asdf</p></bq><p>qwer</p>
	//            ^^^^^^^^^^^^^^^^                        ^^^^
	const leaves = fragment.getSelectedLeafNodes();
	const leavesRange = new ve.Range(
		leaves[ 0 ].getRange().start,
		leaves[ leaves.length - 1 ].getRange().end
	);
	fragment = surfaceModel.getLinearFragment( leavesRange, true );

	fragment
		// Expand to cover entire blockquote
		.expandLinearSelection( 'closest', ve.dm.BlockquoteNode )
		// Unwrap it
		.unwrapNodes( 0, 1 );

	return true;
};

/* Registration */

ve.ui.actionFactory.register( ve.ui.BlockquoteAction );
