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

/**
 * Base class for source-mode completion actions that suggest page titles from
 * a search query, e.g. wikilinks and template transclusions.
 *
 * @class
 * @abstract
 * @extends ve.ui.CompletionAction
 *
 * @constructor
 * @param {ve.ui.Surface} surface Surface to act on
 * @param {string} [source]
 */
ve.ui.MWTitleCompletionAction = function VeUiMWTitleCompletionAction() {
	// Parent constructor
	ve.ui.MWTitleCompletionAction.super.apply( this, arguments );

	// Create a dummy input widget
	// TODO: Abstract methods in TitleInputWidget enough that this isn't necessary.
	this.titleWidget = this.createTitleWidget( {
		showDisambigsLast: true,
		showInterwikis: true,
		searchFragments: true,
		showMissing: true,
		namespace: this.constructor.static.namespace,
		limit: this.constructor.static.defaultLimit,
		api: this.surface.getTarget().getContentApi()
	} );
	this.suggestionsPromise = null;
};

/* Inheritance */

OO.inheritClass( ve.ui.MWTitleCompletionAction, ve.ui.CompletionAction );

/* Static Properties */

// Strip the two-character trigger ('[[' or '{{') from the searched input.
ve.ui.MWTitleCompletionAction.static.sequenceLength = 2;

// This action does not call filterSuggestionsForInput, so nothing adds the input to
// the list. Leaving this true would make CompletionWidget subtract a suggestion that
// is not there when it counts the matches for shouldAbandon.
ve.ui.MWTitleCompletionAction.static.alwaysIncludeInput = false;

/**
 * @property {number|undefined} Namespace to prepend to queries - undefined if none
 * @static
 * @inheritable
 */
ve.ui.MWTitleCompletionAction.static.namespace = undefined;

/**
 * @property {string|null} Message key for the completion menu header
 * @static
 * @inheritable
 */
ve.ui.MWTitleCompletionAction.static.headerMessage = null;

/* Methods */

/**
 * Create the widget whose search logic this action reuses
 *
 * @protected
 * @param {Object} config Configuration options for the widget
 * @return {mw.widgets.TitleInputWidget}
 */
ve.ui.MWTitleCompletionAction.prototype.createTitleWidget = function ( config ) {
	return new mw.widgets.TitleInputWidget( config );
};

/**
 * Prepare the search for an input, and get the leading syntax the search leaves out
 *
 * A leading colon says "not the default namespace". It is not part of the title, and the
 * search finds nothing for a query that starts with one, e.g. ":Category:Foo". A subclass
 * adds the syntax it knows in front of this, in the order the wikitext uses.
 *
 * @protected
 * @param {string} input
 * @return {string} Start of the input to put back in front of each suggestion
 */
ve.ui.MWTitleCompletionAction.prototype.prepareSearch = function ( input ) {
	return input.startsWith( ':' ) ? ':' : '';
};

/**
 * @inheritdoc
 */
ve.ui.MWTitleCompletionAction.prototype.getSuggestions = function ( input ) {
	if ( this.suggestionsPromise ) {
		this.suggestionsPromise.abort();
	}

	const prefix = this.prepareSearch( input ),
		query = input.slice( prefix.length );
	this.titleWidget.setValue( query );

	// getLookupRequest, not getSuggestionsPromise: subclasses of the widget adjust the
	// results there, and the menu must show the same results as the widget would.
	this.suggestionsPromise = this.titleWidget.getLookupRequest();
	return this.suggestionsPromise.then( ( response ) => {
		if ( !response || !response.query ) {
			return [];
		}
		const suggestions = this.titleWidget.getOptionsFromData( response.query )
			// The API limit only limits the prefix search. TitleWidget adds the sources
			// of resolved redirects and the missing-page result on top of that, so cap
			// the list here as filterSuggestionsForInput used to.
			.slice( 0, this.constructor.static.defaultLimit )
			.map(
				// Put back what the search did not get
				// TODO: Find a way to extract the "isInterwiki" flag from the response.
				( option ) => prefix + this.restoreTypedNamespace( option.data, query )
			);
		// The input as typed and the normalized title become the same wikitext here
		return OO.unique( suggestions );
	} );
};

/**
 * Put a namespace prefix back the way it was typed
 *
 * The search answers with canonical titles, so a query for "Image:Foo" comes back as
 * "File:Foo". Keep the prefix the user chose. This also keeps the correct name for a
 * namespace that has a gendered form.
 *
 * @protected
 * @param {string} suggestion Suggestion from the title widget
 * @param {string} query Query the suggestion was found for, without a leading colon
 * @return {string}
 */
ve.ui.MWTitleCompletionAction.prototype.restoreTypedNamespace = function ( suggestion, query ) {
	const colon = query.indexOf( ':' ),
		title = colon > 0 ? mw.Title.newFromText( query ) : null,
		// The main namespace has no prefix. An interwiki prefix also resolves to it, and
		// must stay part of the title
		namespace = title ? title.getNamespaceId() : 0;
	if ( !namespace ) {
		return suggestion;
	}

	const typedPrefix = query.slice( 0, colon ),
		canonicalPrefix = mw.config.get( 'wgFormattedNamespaces' )[ namespace ];
	// Replace the prefix only, so that a section or a subpage of the title survives
	return canonicalPrefix !== typedPrefix && suggestion.startsWith( canonicalPrefix + ':' ) ?
		typedPrefix + suggestion.slice( canonicalPrefix.length ) :
		suggestion;
};

/**
 * Build the wikitext to insert for a chosen suggestion
 *
 * @protected
 * @abstract
 * @param {string} suggestion Chosen suggestion string
 * @return {string} Wikitext to insert, including the opening brackets
 */
ve.ui.MWTitleCompletionAction.prototype.getInsertionText = null;

/**
 * @inheritdoc
 */
ve.ui.MWTitleCompletionAction.prototype.getHeaderLabel = function ( input, suggestions ) {
	const headerMessage = this.constructor.static.headerMessage;
	if ( !headerMessage || ( suggestions && !suggestions.length ) ) {
		// Suppress the header when there are no suggestions, otherwise the menu
		// would stay open showing just the header with an empty list.
		return null;
	}
	return mw.msg( headerMessage );
};

/**
 * @inheritdoc
 */
ve.ui.MWTitleCompletionAction.prototype.insertCompletion = function ( data, range ) {
	// The range covers the opening brackets, so getInsertionText must re-emit them.
	const text = this.getInsertionText( data );
	return ve.ui.MWTitleCompletionAction.super.prototype.insertCompletion.call( this, text, range );
};

/**
 * @inheritdoc
 */
ve.ui.MWTitleCompletionAction.prototype.shouldAbandon = function ( input ) {
	// TODO: consider whether pending loads from server are happening here?
	return ve.ui.MWTitleCompletionAction.super.prototype.shouldAbandon.apply( this, arguments ) ||
		// Abandon if a character that suggests we're moving on from the link-markup has been entered
		/[\]}|]$/.test( input );
};