"use strict";

describe("query-textbox", function () {
	var win, textbox;

	before(async function () {
		win = await loadZoteroPane();
		textbox = win.document.getElementById('zotero-tb-search').searchTextbox;
	});

	after(async function () {
		await Zotero.Tags.setColor(Zotero.Libraries.userLibraryID, 'zzcompletion', false);
		textbox.value = '';
		win.close();
	});

	it("should render the query in spans covering the whole value", function () {
		textbox.value = 'by:smith crispr';
		let layer = textbox.shadowRoot.querySelector('.query-highlight');
		assert.equal(layer.textContent, 'by:smith crispr');
		assert.deepEqual(
			[...layer.children].map(span => span.className),
			['query-token-field', 'query-token-operator', 'query-token-value',
				'query-token-space', 'query-token-text']
		);
		assert.isTrue(textbox.hasAttribute('highlighted'));
	});

	it("should leave a query without conditions to the input", function () {
		textbox.value = 'just some words';
		assert.isFalse(textbox.hasAttribute('highlighted'));
		assert.equal(
			textbox.shadowRoot.querySelector('.query-highlight').textContent, ''
		);
	});

	it("should offer tags and creators from the library", async function () {
		let item = await createDataObject('item', { tags: [{ tag: 'zzcompletion' }] });
		item.setCreator(0, { firstName: 'Zora', lastName: 'Zzcompletion', creatorType: 'author' });
		// A two-field creator with only a last name, whose value has no
		// leading space to quote
		item.setCreator(1, { firstName: '', lastName: 'Zzlastonly', creatorType: 'author' });
		await item.saveTx();

		// updateCompletions() resolves once the library lookup has filled
		// the list
		let offered = async (query, value) => {
			textbox.value = query;
			textbox.inputField.setSelectionRange(query.length, query.length);
			await textbox.updateCompletions();
			let list = win.document.querySelector('.query-completions richlistbox');
			assert.isTrue(
				[...list.children].some(row => row.completion.label === value), value
			);
		};
		await Zotero.Tags.setColor(Zotero.Libraries.userLibraryID, 'zzcompletion', '#FF6666');
		await offered('tag:zzcomp', 'zzcompletion');
		// A colored tag carries its color, for the swatch in the list
		let list = win.document.querySelector('.query-completions richlistbox');
		let row = [...list.children].find(r => r.completion.label === 'zzcompletion');
		assert.equal(row.completion.color, '#FF6666');
		await offered('by:zzcomp', 'Zora Zzcompletion');
		await offered('by:zzlast', 'Zzlastonly');
	});

	it("should offer completions for a condition name and then its values", async function () {
		textbox.value = 'ty';
		textbox.inputField.setSelectionRange(2, 2);
		textbox.updateCompletions();
		let list = win.document.querySelector('.query-completions richlistbox');
		// The list is shown the first time there's something to offer
		assert.notEqual(win.document.querySelector('.query-completions').state, 'closed');
		let labels = [...list.children].map(row => row.completion.label);
		assert.include(labels, 'type:');

		// Completing a condition name offers what it takes next
		list.selectedIndex = labels.indexOf('type:');
		textbox._acceptCompletion();
		assert.equal(textbox.value, 'type:');
		assert.equal(textbox.inputField.selectionStart, 'type:'.length);
		labels = [...list.children].map(row => row.completion.label);
		assert.include(labels, 'journal article');

		// Arrowing up from nothing selected starts at the end of the list
		textbox._handleCompletionKey(new win.KeyboardEvent('keydown', { key: 'ArrowUp' }));
		assert.equal(list.selectedIndex, list.itemCount - 1);

		list.selectedIndex = labels.indexOf('journal article');
		textbox._acceptCompletion();
		assert.equal(textbox.value, 'type:"journal article"');
		assert.deepEqual(
			Zotero.SearchQuery.parse(textbox.value).tree.children,
			[{ condition: 'itemType', operator: 'is', value: 'journalArticle' }]
		);

		textbox.value = 'crispr ';
		textbox.inputField.setSelectionRange(7, 7);
		textbox.updateCompletions();
		assert.equal(win.document.querySelector('.query-completions').state, 'closed');
	});

	it("should accept the completion under the mouse", async function () {
		textbox.value = 'ty';
		textbox.inputField.setSelectionRange(2, 2);
		await textbox.updateCompletions();
		let list = win.document.querySelector('.query-completions richlistbox');
		let row = [...list.children].find(r => r.completion.label === 'type:');
		row.dispatchEvent(new win.MouseEvent('mousedown', { bubbles: true }));
		assert.equal(textbox.value, 'type:');
	});

	it("should not reopen the list after accepting a value", async function () {
		await createDataObject('item', { tags: [{ tag: 'zzaccepted' }] });
		textbox.value = 'tag:zzaccep';
		textbox.inputField.setSelectionRange(11, 11);
		await textbox.updateCompletions();
		let list = win.document.querySelector('.query-completions richlistbox');
		let index = [...list.children].findIndex(row => row.completion.label === 'zzaccepted');
		assert.isAbove(index, -1);
		list.selectedIndex = index;
		// Control the follow-up lookup, so the result demonstrably arrives
		// only after the acceptance has hidden the list
		let resolveLookup;
		let stub = sinon.stub(textbox, '_lookupValues').returns(new Promise((resolve) => {
			resolveLookup = resolve;
		}));
		try {
			textbox._acceptCompletion();
			assert.equal(textbox.value, 'tag:zzaccepted');
			resolveLookup([{ text: 'zzaccepted', label: 'zzaccepted' }]);
			await Zotero.Promise.delay(0);
			assert.include(['closed', 'hiding'],
				win.document.querySelector('.query-completions').state);
		}
		finally {
			stub.restore();
		}
	});

	it("should close the completion list on Enter without a selection", async function () {
		textbox.value = 'ty';
		textbox.inputField.setSelectionRange(2, 2);
		await textbox.updateCompletions();
		assert.notEqual(win.document.querySelector('.query-completions').state, 'closed');
		textbox._handleCompletionKey(new win.KeyboardEvent('keydown', { key: 'Enter' }));
		assert.include(['closed', 'hiding'],
			win.document.querySelector('.query-completions').state);
	});

	it("should hide completions when the value is set", async function () {
		textbox.value = 'ty';
		textbox.inputField.setSelectionRange(2, 2);
		await textbox.updateCompletions();
		assert.notEqual(win.document.querySelector('.query-completions').state, 'closed');
		textbox.value = 'something else';
		assert.include(['closed', 'hiding'],
			win.document.querySelector('.query-completions').state);
	});
});
