/*!
 * VisualEditor UserInterface Tool classes.
 *
 * @copyright See AUTHORS.txt
 */

/**
 * UserInterface tool.
 *
 * @class
 * @abstract
 * @extends OO.ui.Tool
 *
 * @constructor
 * @param {OO.ui.ToolGroup} toolGroup
 * @param {Object} [config] Configuration options
 */
ve.ui.Tool = function VeUiTool() {
	// Parent constructor
	ve.ui.Tool.super.apply( this, arguments );

	// Disable initially
	this.setDisabled( true );
};

/* Inheritance */

OO.inheritClass( ve.ui.Tool, OO.ui.Tool );

/* Static Properties */

/**
 * Command to execute when tool is selected.
 *
 * @static
 * @property {string|null}
 * @inheritable
 */
ve.ui.Tool.static.commandName = null;

/**
 * Deactivate tool after it's been selected.
 *
 * Use this for tools which don't display as active when relevant content is selected, such as
 * insertion-only tools.
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.ui.Tool.static.deactivateOnSelect = true;

/**
 * If this tool is used to create a ve.ui.ToolContextItem, should that item be embeddable
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.ui.Tool.static.makesEmbeddableContextItem = true;

/**
 * Icon to use when this tool is shown in a non-toolbar context
 *
 * @static
 * @property {string|null}
 * @inheritable
 */
ve.ui.Tool.static.fallbackIcon = null;

/**
 * Get the symbolic command name for this tool.
 *
 * @static
 * @return {string|null}
 */
ve.ui.Tool.static.getCommandName = function () {
	return this.commandName;
};

/**
 * Get the command for this tool in a given surface context
 *
 * @static
 * @param {ve.ui.Surface} surface
 * @return {ve.ui.Command|null|undefined} Undefined means command not found, null means no command set
 */
ve.ui.Tool.static.getCommand = function ( surface ) {
	const commandName = this.getCommandName();
	if ( commandName === null ) {
		return null;
	}
	return surface.commandRegistry.lookup( commandName );
};

/* Methods */

/**
 * Handle the toolbar state being updated.
 *
 * @param {ve.dm.SurfaceFragment|null} fragment Surface fragment
 * @param {Object|null} direction Context direction with 'inline' & 'block' properties
 */
ve.ui.Tool.prototype.onUpdateState = function ( fragment ) {
	const command = this.getCommand();
	if ( command !== null ) {
		this.setDisabled(
			!command || !fragment || !command.isExecutable( fragment ) ||
			// Show command as disabled if a selection is required and the surface
			// is read-only. Don't do this in Command as some are actually allowed
			// to run, e.g. selectAll
			// TODO: Add an allowedInReadOnly flag to some commands
			( command.supportedSelections && fragment.getSurface() && fragment.getSurface().isReadOnly() )
		);
	}
};

/**
 * @inheritdoc
 */
ve.ui.Tool.prototype.onSelect = function () {
	const command = this.getCommand(),
		surface = this.toolbar.getSurface();

	let contextClosePromise;
	if ( command instanceof ve.ui.Command ) {
		if ( surface.context.inspector ) {
			contextClosePromise = surface.context.inspector.close().closed;
		} else {
			contextClosePromise = ve.createDeferred().resolve().promise();
		}
	}
	if ( this.constructor.static.deactivateOnSelect ) {
		// It's fine to call setActive here before the promise resolves; it
		// just disables the button, stopping double-clicks and making it feel more responsive
		// if the promise is slow.
		this.setActive( false );
	}
	if ( contextClosePromise ) {
		// N.B. If contextClosePromise is already resolved, then the handler is called
		// before the call to .done returns
		contextClosePromise.done( () => {
			if ( !command.execute( surface, undefined, 'tool' ) ) {
				// If the command fails, ensure the tool is not active
				this.setActive( false );
			}
		} );
	}
};

/**
 * Get the command for this tool.
 *
 * @return {ve.ui.Command|null|undefined} Undefined means command not found, null means no command set
 */
ve.ui.Tool.prototype.getCommand = function () {
	return this.constructor.static.getCommand( this.toolbar.getSurface() );
};