'use strict';

/*!
 * VisualEditor UserInterface MWReferenceSearchWidget class.
 *
 * @copyright 2011-2018 VisualEditor Team's Cite sub-team and others; see AUTHORS.txt
 * @license MIT
 */

const MWDocumentReferences = require( './ve.dm.MWDocumentReferences.js' );
const MWReferenceModel = require( './ve.dm.MWReferenceModel.js' );
const MWReferenceResultWidget = require( './ve.ui.MWReferenceResultWidget.js' );

/**
 * Creates an ve.ui.MWReferenceSearchWidget object.
 *
 * @constructor
 * @extends OO.ui.SearchWidget
 * @param {Object} config Configuration options
 * @param {jQuery} config.$overlay Layer to render reuse submenu outside of the parent dialog
 * @property {Object[]|null} index Null when the index needs to be rebuild
 */
ve.ui.MWReferenceSearchWidget = function VeUiMWReferenceSearchWidget( config ) {
	// Configuration initialization
	config = ve.extendObject( {
		placeholder: ve.msg( 'cite-ve-reference-input-placeholder' )
	}, config );

	// Parent constructor
	ve.ui.MWReferenceSearchWidget.super.call( this, config );

	// Properties
	this.docRefs = null;
	this.index = null;
	this.wasUsedActively = false;
	this.$overlay = config.$overlay;

	// Initialization
	this.$element.addClass( 've-ui-mwReferenceSearchWidget' );
	this.$results.on( 'scroll', this.trackActiveUsage.bind( this ) );
	this.getResults().connect( this, { choose: 'onChoose' } );

};

/* Inheritance */

OO.inheritClass( ve.ui.MWReferenceSearchWidget, OO.ui.SearchWidget );

/* Events */

/**
 * User chose a ref for reuse
 *
 * @event ve.ui.MWReferenceSearchWidget#reuse
 * @param {MWReferenceModel} ref
 */

/* Methods */

ve.ui.MWReferenceSearchWidget.prototype.onQueryChange = function () {
	// Parent method
	ve.ui.MWReferenceSearchWidget.super.prototype.onQueryChange.call( this );

	// Populate
	this.getResults().addItems( this.buildSearchResults( this.getQuery().getValue() ) );
};

/**
 * @param {jQuery.Event} e Key down event
 */
ve.ui.MWReferenceSearchWidget.prototype.onQueryKeydown = function ( e ) {
	if ( e.which === OO.ui.Keys.TAB && !e.shiftKey &&
		!this.results.isEmpty() &&
		!this.results.findHighlightedItem()
	) {
		this.results.highlightItem( this.results.findFirstSelectableItem() );
		e.preventDefault();
	}

	// Parent method
	ve.ui.MWReferenceSearchWidget.super.prototype.onQueryKeydown.call( this, e );

	this.trackActiveUsage();
};

ve.ui.MWReferenceSearchWidget.prototype.clearSearch = function () {
	this.getQuery().setValue( '' );
	this.wasUsedActively = false;
};

/**
 * Track when the user looks for references to reuse using scrolling or filtering results
 */
ve.ui.MWReferenceSearchWidget.prototype.trackActiveUsage = function () {
	if ( this.wasUsedActively ) {
		return;
	}

	// https://phabricator.wikimedia.org/T362347
	ve.track( 'activity.reference', { action: 'reuse-dialog-use' } );
	this.wasUsedActively = true;
};

ve.ui.MWReferenceSearchWidget.prototype.onChoose = function ( item ) {
	this.emit( 'reuse', item.getData() );
};

/**
 * Set the internal list and check if it contains any references
 *
 * @param {MWDocumentReferences} docRefs handle to all refs in the original document
 */
ve.ui.MWReferenceSearchWidget.prototype.setDocumentRefs = function ( docRefs ) {
	this.results.unselectItem();

	this.docRefs = docRefs;
};

/**
 * Set the internal list and check if it contains any references
 *
 * @deprecated use #setDocumentRefs instead.
 * @param {ve.dm.InternalList} internalList
 */
ve.ui.MWReferenceSearchWidget.prototype.setInternalList = function ( internalList ) {
	this.setDocumentRefs( MWDocumentReferences.static.refsForDoc( internalList.getDocument() ) );
};

/**
 * Manually re-build the index and re-populate the list of search results.
 */
ve.ui.MWReferenceSearchWidget.prototype.buildIndex = function () {
	this.index = null;
	this.onQueryChange();
};

/**
 * @private
 * @return {Object[]}
 */
ve.ui.MWReferenceSearchWidget.prototype.buildSearchIndex = function () {
	const groupNames = this.docRefs.getAllGroupNames().sort();

	// FIXME: Temporary hack, to be removed soon
	// eslint-disable-next-line no-jquery/no-class-state
	const filterExtends = this.$element.hasClass( 've-ui-citoidInspector-extends' );

	let index = [];
	for ( let i = 0; i < groupNames.length; i++ ) {
		const groupName = groupNames[ i ];
		if ( !groupName.startsWith( 'mwReference/' ) ) {
			// FIXME: Should be impossible to reach
			continue;
		}
		const groupRefs = this.docRefs.getGroupRefs( groupName );
		const flatNodes = groupRefs.getAllRefsInReflistOrder()
			.filter( ( node ) => !filterExtends || !node.getAttribute( 'mainRefKey' ) );

		index = index.concat( flatNodes.map( ( node ) => {
			const listKey = node.getAttribute( 'listKey' );
			// remove `mwReference/` prefix
			const group = groupName.slice( 12 );
			const footnoteNumber = this.docRefs.getIndexLabel( group, listKey );
			const footnoteLabel = ( group ? group + ' ' : '' ) + footnoteNumber;

			// Use [\s\S]* instead of .* to catch esoteric whitespace (T263698)
			const matches = listKey.match( /^literal\/([\s\S]*)$/ );
			const name = matches && matches[ 1 ] || '';

			let $refContent;
			// Make visible text, footnoteLabel and reference name searchable
			let refText = footnoteLabel + ' ' + name;
			const itemNode = groupRefs.getInternalModelNode( listKey );
			if ( itemNode.length ) {
				$refContent = new ve.ui.MWPreviewElement( itemNode, { useView: true } ).$element;
				refText = $refContent.text() + ' ' + refText;
				// Make URLs searchable
				$refContent.find( 'a[href]' ).each( ( _, element ) => {
					refText += ' ' + element.getAttribute( 'href' );
				} );
			} else {
				$refContent = $( '<span>' )
					.addClass( 've-ce-mwReferencesListNode-muted' )
					.text( ve.msg( 'cite-ve-referenceslist-missingref-in-list' ) );
			}

			return {
				$refContent,
				searchableText: refText.toLowerCase(),
				// TODO: return a simple node
				reference: MWReferenceModel.static.newFromReferenceNode( node ),
				footnoteLabel,
				name
			};
		} ) );
	}

	return index;
};

/**
 * @return {boolean} Index is empty
 */
ve.ui.MWReferenceSearchWidget.prototype.isIndexEmpty = function () {
	return !this.docRefs.hasRefs();
};

/**
 * @private
 * @param {string} query
 * @return {MWReferenceResultWidget[]}
 */
ve.ui.MWReferenceSearchWidget.prototype.buildSearchResults = function ( query ) {
	query = query.trim().toLowerCase();
	const results = [];

	if ( !this.index ) {
		this.index = this.buildSearchIndex();
	}

	this.index.forEach( ( item ) => {
		if ( item.searchableText.includes( query ) ) {
			results.push( new MWReferenceResultWidget( { item } ) );
		}
	} );

	return results;
};

module.exports = ve.ui.MWReferenceSearchWidget;
