{
	const toggleCirrusSearchLookup = ( enabled ) => mw.config.set( 'wgVisualEditorConfig', ve.extendObject( {}, mw.config.get( 'wgVisualEditorConfig' ), {
			cirrusSearchLookup: enabled !== false
		} ) );

	QUnit.module( 've.ui.MWTemplateTitleInputWidget', ve.test.utils.newMwEnvironment( {
		messages: {
			// Force `templateDataInstalled` condition
			'templatedata-doc-subpage': '(templatedata-doc-subpage)',
			'visualeditor-template-sandbox-subpage': '(template-sandbox-subpage)'
		},
		// Config will be reset by newMwEnvironment's teardown
		beforeEach: function () {
			this.server = this.sandbox.useFakeServer();
			toggleCirrusSearchLookup();
		}
	} ) );

	QUnit.test( 'default prefixsearch', ( assert ) => {
		toggleCirrusSearchLookup( false );

		const widget = new ve.ui.MWTemplateTitleInputWidget();
		const query = 'a';
		const apiParams = widget.getApiParams( query );

		assert.deepEqual( apiParams, {
			action: 'query',
			generator: 'prefixsearch',
			gpslimit: 10,
			gpsnamespace: 10,
			gpssearch: 'a',
			ppprop: 'disambiguation',
			prop: [ 'info', 'pageprops' ],
			redirects: true
		} );
	} );

	QUnit.test( 'CirrusSearch: all API parameters', ( assert ) => {
		const widget = new ve.ui.MWTemplateTitleInputWidget();
		const query = 'a';
		const apiParams = widget.getApiParams( query );

		assert.deepEqual( apiParams, {
			action: 'query',
			generator: 'search',
			gsrlimit: 10,
			gsrnamespace: 10,
			gsrprop: 'redirecttitle',
			gsrsearch: 'a*',
			ppprop: 'disambiguation',
			prop: [ 'info', 'pageprops' ],
			redirects: true
		} );
	} );

	QUnit.test( 'CirrusSearch: showRedirectTargets disabled', ( assert ) => {
		const widget = new ve.ui.MWTemplateTitleInputWidget( { showRedirectTargets: false } ),
			apiParams = widget.getApiParams();

		assert.false( 'gsrprop' in apiParams );
	} );

	QUnit.test.each( 'CirrusSearch: prefixsearch behavior', [
		{
			query: 'a',
			expected: 'a*'
		},
		{
			query: 'a ',
			expected: 'a '
		},
		{
			query: 'ü',
			expected: 'ü*'
		},
		{
			query: '3',
			expected: '3*'
		},
		{
			query: '!!',
			expected: '!!'
		},
		{
			query: 'Foo:',
			expected: 'Foo:'
		},
		{
			query: 'Foo:Bar',
			expected: 'Foo:Bar*'
		},
		{
			query: 'foo_',
			expected: 'foo_'
		},
		{
			query: 'foo-',
			expected: 'foo-'
		},
		{
			query: 'foo+',
			expected: 'foo+'
		},
		{
			query: 'foo/',
			expected: 'foo/'
		},
		{
			query: 'foo~',
			expected: 'foo~'
		},
		{
			query: 'foo*',
			expected: 'foo*'
		},
		{
			query: '(foo)',
			expected: '(foo)'
		},
		{
			query: '[foo]',
			expected: '[foo]'
		},
		{
			query: '{foo}',
			expected: '{foo}'
		},
		{
			query: '"foo"',
			expected: '"foo"'
		},
		{
			query: 'foß',
			expected: 'foß*'
		},
		{
			query: '中文字',
			expected: '中文字*'
		},
		{
			query: 'zhōngwénzì',
			expected: 'zhōngwénzì*'
		}
	], ( assert, data ) => {
		const widget = new ve.ui.MWTemplateTitleInputWidget();
		const apiParams = widget.getApiParams( data.query );

		assert.strictEqual(
			apiParams.gsrsearch,
			data.expected,
			'Searching for ' + data.query
		);
	} );

	QUnit.test( 'CirrusSearch with prefixsearch fallback', async function ( assert ) {
		const api = new mw.Api();
		this.sandbox.stub( api, 'get' )
			.onFirstCall().returns( ve.createDeferred()
				.resolve( { query: {
					pages: [
						{ pageid: 101, title: 'B' },
						{ pageid: 102, title: 'A' },
						// Documentation subpage, expected to be stripped
						{ pageid: 103, title: 'A/(templatedata-doc-subpage)', index: 2 },
						// Sandbox subpage, expected to be stripped
						{ pageid: 104, title: 'A/(template-sandbox-subpage)', index: 3 }
					],
					redirects: [
						// Alternative source for indexes, expected to be copied to the pages array
						{ from: '', to: 'B', index: 1 },
						{ from: '', to: 'A', index: 0 }
					]
				} } )
				.promise( { abort: () => {} } )
			)
			.onSecondCall().returns( ve.createDeferred()
				.resolve( { query: { pages: [
					// Duplicate found by CirrusSearch (above) and prefixsearch
					{ pageid: 102, title: 'A', index: 2 },
					// New prefixsearch matches, expected to be prepended, in order of relevance
					{ pageid: 201, title: 'D', index: 1 },
					{ pageid: 202, title: 'C', index: 0 }
				] } } )
			)
			.onThirdCall().returns( ve.createDeferred()
				.resolve( /* we can skip the templatedata request for this test */ )
			);

		const widget = new ve.ui.MWTemplateTitleInputWidget( { api, showDescriptions: true } );
		widget.setValue( 'something' );

		const response = await widget.getLookupRequest();
		assert.deepEqual( response.query.pages, [
			{ pageid: 202, title: 'C', index: -10 },
			{ pageid: 201, title: 'D', index: -9 },
			{ pageid: 102, title: 'A', index: 0 },
			{ pageid: 101, title: 'B', index: 1 }
		] );
	} );

	QUnit.test( 'CirrusSearch: redirect is forwarded to the TitleOptionWidget', ( assert ) => {
		const widget = new ve.ui.MWTemplateTitleInputWidget();
		const originalData = { redirecttitle: 'Template:From' };
		const data = widget.getOptionWidgetData( 'Template:To', { originalData } );

		assert.strictEqual( data.redirecttitle, 'Template:From' );
	} );

	QUnit.test( 'CirrusSearch: redirect appears in the description', ( assert ) => {
		const widget = new ve.ui.MWTemplateTitleInputWidget();

		let option = widget.createOptionWidget( { redirecttitle: 'Template:From' } );
		assert.strictEqual(
			option.$element.find( '.ve-ui-mwTemplateTitleInputWidget-redirectedfrom' ).text(),
			'(redirectedfrom: From)'
		);

		widget.relative = false;
		option = widget.createOptionWidget( { redirecttitle: 'Template:From' } );
		assert.strictEqual(
			option.$element.find( '.ve-ui-mwTemplateTitleInputWidget-redirectedfrom' ).text(),
			'(redirectedfrom: Template:From)'
		);
	} );
}
