(function($){
	'use strict';

	var state = {
		matches: [],
		currentIndex: -1,
		lastFind: '',
		lastCaseSensitive: false,
		lastContentSource: '',
		blockEditorNoticeShown: false,
		undoSnapshot: null
	};
	var highlightOverlays = {};
	var highlightFrame = null;
	var replaceOpenKey = 'consortiaqfrAdminReplaceOpen';
	var screenFindKeyPrefix = 'consortiaqfrAdminScreenFind:';
	var screenReplaceKeyPrefix = 'consortiaqfrAdminScreenReplace:';
	var cssHighlightNames = {
		match: 'consortiaqfr-match',
		active: 'consortiaqfr-active-match'
	};

	function escRegex(text) {
		return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
	}

	function getI18n(key, fallback) {
		if (window.consortiaqfrAdmin && consortiaqfrAdmin.i18n && consortiaqfrAdmin.i18n[key]) {
			return consortiaqfrAdmin.i18n[key];
		}

		return fallback;
	}

	function getTitleField() {
		return document.getElementById('title');
	}

	function getTitleValue() {
		var titleField = getTitleField();

		if (titleField) {
			return String(titleField.value || '');
		}

		if (hasBlockEditor() && wp.data.select('core/editor').getEditedPostAttribute) {
			return String(wp.data.select('core/editor').getEditedPostAttribute('title') || '');
		}

		return '';
	}

	function setTitleValue(value) {
		var titleField = getTitleField();

		if (titleField) {
			titleField.value = value;
		}

		if (hasBlockEditor()) {
			wp.data.dispatch('core/editor').editPost({ title: value });
		}
	}

	function hasBlockEditor() {
		return !!(window.wp && wp.data && wp.data.select && wp.data.dispatch && wp.data.select('core/editor'));
	}

	function isTinyMceActive() {
		return !!(window.tinymce && tinymce.get('content') && !tinymce.get('content').isHidden());
	}

	function isVisibleElement(element) {
		return !!(element && (element.offsetWidth || element.offsetHeight || element.getClientRects().length));
	}

	function getStoredReplaceOpenState() {
		try {
			return window.localStorage.getItem(replaceOpenKey) || '';
		} catch (error) {
			return '';
		}
	}

	function storeReplaceOpenState(isOpen) {
		try {
			window.localStorage.setItem(replaceOpenKey, isOpen ? 'open' : 'closed');
		} catch (error) {
			// Ignore storage errors from private browsing or blocked storage.
		}
	}

	function setReplaceSectionOpen(isOpen) {
		var replaceWrap = document.getElementById('consortia-qfr-replace-wrap');

		if (!replaceWrap) {
			return;
		}

		replaceWrap.open = !!isOpen;
		storeReplaceOpenState(!!isOpen);
	}

	function restoreReplaceSectionState() {
		var replaceWrap = document.getElementById('consortia-qfr-replace-wrap');
		var storedState;

		if (!replaceWrap) {
			return;
		}

		storedState = getStoredReplaceOpenState();

		if (storedState === 'open') {
			replaceWrap.open = true;
			return;
		}

		if (storedState === 'closed') {
			replaceWrap.open = false;
		}
	}

	function getCurrentScreenFindKey() {
		var params = new window.URLSearchParams(window.location.search);

		return screenFindKeyPrefix + [
			window.location.pathname || '',
			params.get('post') || '',
			params.get('post_type') || '',
			params.get('action') || ''
		].join(':');
	}

	function getCurrentScreenReplaceKey() {
		var params = new window.URLSearchParams(window.location.search);

		return screenReplaceKeyPrefix + [
			window.location.pathname || '',
			params.get('post') || '',
			params.get('post_type') || '',
			params.get('action') || ''
		].join(':');
	}

	function storeScreenFindValue(value) {
		var key = getCurrentScreenFindKey();
		var normalized = String(value || '');

		try {
			if (normalized) {
				window.localStorage.setItem(key, normalized);
			} else {
				window.localStorage.removeItem(key);
			}
		} catch (error) {
			// Ignore storage errors from private browsing or blocked storage.
		}
	}

	function restoreScreenFindValue() {
		var key;
		var value;

		if ($('#consortia-qfr-find').val()) {
			return;
		}

		key = getCurrentScreenFindKey();

		try {
			value = window.localStorage.getItem(key) || '';
		} catch (error) {
			value = '';
		}

		if (value) {
			$('#consortia-qfr-find').val(value);
		}
	}

	function storeScreenReplaceValue(value) {
		var key = getCurrentScreenReplaceKey();
		var normalized = String(value || '');

		try {
			if (normalized) {
				window.localStorage.setItem(key, normalized);
			} else {
				window.localStorage.removeItem(key);
			}
		} catch (error) {
			// Ignore storage errors from private browsing or blocked storage.
		}
	}

	function restoreScreenReplaceValue() {
		var key;
		var value;

		if ($('#consortia-qfr-replace').val()) {
			return;
		}

		key = getCurrentScreenReplaceKey();

		try {
			value = window.localStorage.getItem(key) || '';
		} catch (error) {
			value = '';
		}

		if (value) {
			$('#consortia-qfr-replace').val(value);
			setReplaceSectionOpen(true);
		}
	}

	function getBlockCodeEditorTextarea() {
		var selectors = [
			'textarea.editor-post-text-editor',
			'.edit-post-text-editor__body textarea',
			'.block-editor-code-editor textarea'
		];
		var textarea = null;

		selectors.some(function(selector) {
			var candidate = document.querySelector(selector);

			if (isVisibleElement(candidate)) {
				textarea = candidate;
				return true;
			}

			return false;
		});

		return textarea;
	}

	function getContentSource() {
		if (getBlockCodeEditorTextarea()) {
			return 'block-code';
		}

		if (hasBlockEditor()) {
			return 'block';
		}

		if (isTinyMceActive()) {
			return 'tinymce';
		}

		return 'textarea';
	}

	function getContentValue() {
		var source = getContentSource();
		var textarea;
		var editedContent;

		if (source === 'block-code') {
			textarea = getBlockCodeEditorTextarea();
			return textarea ? String(textarea.value || '') : '';
		}

		if (source === 'block') {
			editedContent = String(wp.data.select('core/editor').getEditedPostContent() || '');

			if (editedContent) {
				return editedContent;
			}

			if (window.wp && wp.blocks && typeof wp.blocks.serialize === 'function' && wp.data.select('core/block-editor') && typeof wp.data.select('core/block-editor').getBlocks === 'function') {
				return String(wp.blocks.serialize(wp.data.select('core/block-editor').getBlocks()) || '');
			}

			return '';
		}

		if (source === 'tinymce') {
			return String(tinymce.get('content').getContent({ format: 'raw' }) || '');
		}

		var textarea = document.getElementById('content');
		return textarea ? String(textarea.value || '') : '';
	}

	function setContentValue(value) {
		var source = getContentSource();
		var textarea;
		var parsedBlocks;

		if (source === 'block-code') {
			textarea = getBlockCodeEditorTextarea();
			if (textarea) {
				textarea.value = value;
			}
			wp.data.dispatch('core/editor').editPost({ content: value });
			return;
		}

		if (source === 'block') {
			if (
				window.wp &&
				wp.blocks &&
				typeof wp.blocks.parse === 'function' &&
				wp.data.dispatch('core/block-editor') &&
				typeof wp.data.dispatch('core/block-editor').resetBlocks === 'function'
			) {
				parsedBlocks = wp.blocks.parse(value);
				wp.data.dispatch('core/block-editor').resetBlocks(parsedBlocks);
			}

			wp.data.dispatch('core/editor').editPost({ content: value });
			if (!state.blockEditorNoticeShown) {
				setStatus(getI18n('gutenbergNote', 'Block editor support is basic in this first version: find/replace works, but visual highlighting is limited.'), false);
				state.blockEditorNoticeShown = true;
			}
			return;
		}

		if (source === 'tinymce') {
			tinymce.get('content').setContent(value, { format: 'raw' });
			var textareaSync = document.getElementById('content');
			if (textareaSync) {
				textareaSync.value = value;
			}
			return;
		}

		var textarea = document.getElementById('content');
		if (textarea) {
			textarea.value = value;
		}
	}

	function getHighlightableElement(target) {
		if (target === 'title') {
			return getTitleField();
		}

		if (target === 'content') {
			if (getContentSource() === 'block-code') {
				return getBlockCodeEditorTextarea();
			}

			if (getContentSource() === 'textarea') {
				return document.getElementById('content');
			}
		}

		return null;
	}

	function escHtml(text) {
		return String(text)
			.replace(/&/g, '&amp;')
			.replace(/</g, '&lt;')
			.replace(/>/g, '&gt;')
			.replace(/"/g, '&quot;')
			.replace(/'/g, '&#039;');
	}

	function getMatchesForTarget(target) {
		return state.matches.filter(function(match) {
			return match.target === target;
		});
	}

	function getEditorDocuments() {
		var docs = [ document ];

		$('iframe').each(function() {
			try {
				if (this.contentDocument && this.contentDocument.body) {
					docs.push(this.contentDocument);
				}
			} catch (error) {
				// Ignore cross-origin frames.
			}
		});

		return docs.filter(function(doc, index, allDocs) {
			return doc && allDocs.indexOf(doc) === index;
		});
	}

	function supportsCssHighlights(win) {
		return !!(win && win.CSS && win.CSS.highlights && typeof win.Highlight === 'function');
	}

	function clearCssHighlights() {
		getEditorDocuments().forEach(function(doc) {
			var win = doc.defaultView;

			if (!supportsCssHighlights(win)) {
				return;
			}

			win.CSS.highlights.delete(cssHighlightNames.match);
			win.CSS.highlights.delete(cssHighlightNames.active);
		});
	}

	function installCssHighlightStyles(doc) {
		var style;

		if (!doc || doc.getElementById('consortia-qfr-highlight-styles')) {
			return;
		}

		style = doc.createElement('style');
		style.id = 'consortia-qfr-highlight-styles';
		style.textContent =
			'::highlight(consortiaqfr-match){background-color:rgba(255,224,102,0.45);}' +
			'::highlight(consortiaqfr-active-match){background-color:rgba(255,193,7,0.78);}';
		(doc.head || doc.documentElement).appendChild(style);
	}

	function clearOverlayHighlights() {
		Object.keys(highlightOverlays).forEach(function(target) {
			highlightOverlays[target].remove();
			delete highlightOverlays[target];
		});

		$('.consortia-qfr-highlight-source').removeClass('consortia-qfr-highlight-source');
	}

	function isEditorTextNode(node) {
		if (!node || !node.parentElement || !node.nodeValue) {
			return false;
		}

		if (node.parentElement.closest('#consortia-qfr')) {
			return false;
		}

		if (node.parentElement.closest('script, style, textarea, input')) {
			return false;
		}

		return !!node.nodeValue.trim();
	}

	function isInsideBlockTitle(node) {
		if (!node || !node.parentElement) {
			return false;
		}

		return !!node.parentElement.closest(
			'.editor-post-title__input, .wp-block-post-title, [data-type="core/post-title"], [aria-label="Add title"]'
		);
	}

	function createRangeForTextNode(node, start, end) {
		var range = node.ownerDocument.createRange();
		range.setStart(node, start);
		range.setEnd(node, end);
		return range;
	}

	function collectTextRanges(root, find, caseSensitive, target) {
		var ranges = [];
		var needle = caseSensitive ? find : find.toLowerCase();
		var walker;

		if (!root || !find) {
			return ranges;
		}

		walker = root.ownerDocument.createTreeWalker(root, root.ownerDocument.defaultView.NodeFilter.SHOW_TEXT, {
			acceptNode: function(node) {
				if (!isEditorTextNode(node)) {
					return root.ownerDocument.defaultView.NodeFilter.FILTER_REJECT;
				}

				if (target === 'content' && isInsideBlockTitle(node)) {
					return root.ownerDocument.defaultView.NodeFilter.FILTER_REJECT;
				}

				return root.ownerDocument.defaultView.NodeFilter.FILTER_ACCEPT;
			}
		});

		while (walker.nextNode()) {
			var node = walker.currentNode;
			var text = String(node.nodeValue || '');
			var haystack = caseSensitive ? text : text.toLowerCase();
			var index = haystack.indexOf(needle);

			while (index !== -1) {
				ranges.push({
					range: createRangeForTextNode(node, index, index + find.length),
					target: target,
					start: index,
					end: index + find.length
				});
				index = haystack.indexOf(needle, index + find.length);
			}
		}

		return ranges;
	}

	function getBlockTitleRoots(doc) {
		return [
			'.editor-post-title__input',
			'.wp-block-post-title',
			'[data-type="core/post-title"] [contenteditable="true"]',
			'[aria-label="Add title"]'
		].map(function(selector) {
			return doc.querySelector(selector);
		}).filter(function(element, index, elements) {
			return element && elements.indexOf(element) === index;
		});
	}

	function getBlockContentRoots(doc) {
		var editorStylesWrapper = doc.querySelector('.editor-styles-wrapper');
		var writingFlow = doc.querySelector('.block-editor-writing-flow');
		var editableRegion = doc.querySelector('[contenteditable="true"]');

		if (editorStylesWrapper) {
			return [ editorStylesWrapper ];
		}

		if (writingFlow) {
			return [ writingFlow ];
		}

		if (doc !== document && editableRegion) {
			return [ editableRegion ];
		}

		if (doc !== document && doc.body) {
			return [ doc.body ];
		}

		return [];
	}

	function getTinyMceHighlightRoots() {
		var roots = [];
		var contentEditor;

		if (getContentSource() === 'tinymce') {
			contentEditor = tinymce.get('content');
			if (contentEditor && contentEditor.getBody()) {
				roots.push({
					doc: contentEditor.getDoc(),
					root: contentEditor.getBody(),
					target: 'content'
				});
			}
		}

		return roots;
	}

	function applyCssHighlightsToRoots(rootConfigs, find, caseSensitive, activeMatch) {
		var docs = new Map();
		var activeVisualIndex = activeMatch ? getTargetMatchVisualIndex(activeMatch) : -1;

		rootConfigs.forEach(function(config) {
			var items;
			var entry;

			if (!config.doc || !config.root) {
				return;
			}

			items = collectTextRanges(config.root, find, caseSensitive, config.target);

			if (!docs.has(config.doc)) {
				docs.set(config.doc, []);
			}

			entry = docs.get(config.doc);
			Array.prototype.push.apply(entry, items);
		});

		docs.forEach(function(items, doc) {
			var win = doc.defaultView;
			var matchHighlight;
			var activeHighlight;
			var targetIndexes = {};

			if (!supportsCssHighlights(win)) {
				return;
			}

			installCssHighlightStyles(doc);
			matchHighlight = new win.Highlight();
			activeHighlight = new win.Highlight();

			items.forEach(function(item) {
				var itemIndex = targetIndexes[item.target] || 0;

				targetIndexes[item.target] = itemIndex + 1;

				if (
					activeMatch &&
					activeMatch.target === item.target &&
					itemIndex === activeVisualIndex
				) {
					activeHighlight.add(item.range);
					return;
				}

				matchHighlight.add(item.range);
			});

			win.CSS.highlights.set(cssHighlightNames.match, matchHighlight);
			win.CSS.highlights.set(cssHighlightNames.active, activeHighlight);
		});
	}

	function updateBlockEditorHighlights() {
		var find = $('#consortia-qfr-find').val();
		var caseSensitive = $('#consortia-qfr-match-case').is(':checked');
		var activeMatch = state.matches[state.currentIndex] || null;
		var roots = [];

		clearCssHighlights();

		if (!find) {
			return;
		}

		getEditorDocuments().forEach(function(doc) {
			if ($('#consortia-qfr-also-title').is(':checked')) {
				getBlockTitleRoots(doc).forEach(function(root) {
					roots.push({
						doc: doc,
						root: root,
						target: 'title'
					});
				});
			}

			getBlockContentRoots(doc).forEach(function(root) {
				roots.push({
					doc: doc,
					root: root,
					target: 'content'
				});
			});
		});

		applyCssHighlightsToRoots(roots, find, caseSensitive, activeMatch);
	}

	function updateTinyMceHighlights() {
		var find = $('#consortia-qfr-find').val();
		var caseSensitive = $('#consortia-qfr-match-case').is(':checked');
		var activeMatch = state.matches[state.currentIndex] || null;

		clearCssHighlights();

		if (!find) {
			return;
		}

		applyCssHighlightsToRoots(getTinyMceHighlightRoots(), find, caseSensitive, activeMatch);
	}

	function getOrCreateHighlightOverlay(target, element) {
		if (!highlightOverlays[target]) {
			highlightOverlays[target] = $('<div />', {
				'class': 'consortia-qfr-highlight-overlay',
				'aria-hidden': 'true'
			}).appendTo(document.body)[0];
		}

		if (element.tagName && element.tagName.toLowerCase() === 'textarea') {
			highlightOverlays[target].classList.add('is-textarea');
			highlightOverlays[target].classList.remove('is-input');
		} else {
			highlightOverlays[target].classList.add('is-input');
			highlightOverlays[target].classList.remove('is-textarea');
		}

		element.classList.add('consortia-qfr-highlight-source');
		return highlightOverlays[target];
	}

	function syncHighlightOverlayPosition(overlay, element) {
		var rect = element.getBoundingClientRect();
		var styles = window.getComputedStyle(element);
		var copiedProperties = [
			'fontFamily', 'fontSize', 'fontStyle', 'fontWeight', 'letterSpacing', 'lineHeight',
			'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft',
			'borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth'
		];

		overlay.style.left = (rect.left + window.pageXOffset) + 'px';
		overlay.style.top = (rect.top + window.pageYOffset) + 'px';
		overlay.style.width = rect.width + 'px';
		overlay.style.height = rect.height + 'px';

		copiedProperties.forEach(function(property) {
			overlay.style[property] = styles[property];
		});

		overlay.scrollTop = element.scrollTop || 0;
		overlay.scrollLeft = element.scrollLeft || 0;
	}

	function renderHighlightedText(text, matches, activeMatch) {
		var html = '';
		var index = 0;

		matches.forEach(function(match) {
			if (match.start < index) {
				return;
			}

			html += escHtml(text.slice(index, match.start));
			html += '<mark class="consortia-qfr-highlight' + (match === activeMatch ? ' is-active' : '') + '">';
			html += escHtml(text.slice(match.start, match.end));
			html += '</mark>';
			index = match.end;
		});

		html += escHtml(text.slice(index));

		if (!html) {
			html = '&nbsp;';
		}

		return html;
	}

	function updateHighlights() {
		var targets = [ 'title', 'content' ];
		var activeMatch = state.matches[state.currentIndex] || null;
		var source = getContentSource();
		var find = $('#consortia-qfr-find').val();

		if (state.lastContentSource && state.lastContentSource !== source) {
			clearOverlayHighlights();
			clearCssHighlights();
			state.lastContentSource = source;

			if (find) {
				buildMatches({
					preferredMatch: getCurrentMatchLocator()
				});
			}

			return;
		}

		state.lastContentSource = source;

		if (source === 'block') {
			clearOverlayHighlights();
			updateBlockEditorHighlights();
			return;
		}

		if (source === 'tinymce') {
			updateTinyMceHighlights();
		} else {
			clearCssHighlights();
		}

		targets.forEach(function(target) {
			var element = getHighlightableElement(target);
			var matches = getMatchesForTarget(target);
			var overlay;

			if (!element || !matches.length) {
				if (highlightOverlays[target]) {
					highlightOverlays[target].remove();
					delete highlightOverlays[target];
				}
				if (element) {
					element.classList.remove('consortia-qfr-highlight-source');
				}
				return;
			}

			overlay = getOrCreateHighlightOverlay(target, element);
			syncHighlightOverlayPosition(overlay, element);
			overlay.innerHTML = renderHighlightedText(String(element.value || ''), matches, activeMatch);
		});
	}

	function scheduleHighlightUpdate() {
		if (highlightFrame) {
			window.cancelAnimationFrame(highlightFrame);
		}

		highlightFrame = window.requestAnimationFrame(function() {
			highlightFrame = null;
			updateHighlights();
		});
	}

	function getReplaceTargets() {
		var targets = [
			{
				key: 'content',
				value: getContentValue()
			}
		];

		if ($('#consortia-qfr-also-title').is(':checked')) {
			targets.unshift({
				key: 'title',
				value: getTitleValue()
			});
		}

		return targets;
	}

	function shouldIncludeUrlsAndHtml() {
		return $('#consortia-qfr-include-urls').is(':checked');
	}

	function shouldTreatTargetAsHtmlText(targetKey) {
		return !shouldIncludeUrlsAndHtml() && targetKey === 'content';
	}

	function findMatchesInPlainText(value, find, caseSensitive) {
		var flags = caseSensitive ? 'g' : 'gi';
		var regex = new RegExp(escRegex(find), flags);
		var source = String(value || '');
		var matches = [];
		var match;

		while ((match = regex.exec(source)) !== null) {
			matches.push({
				start: match.index,
				end: match.index + match[0].length,
				text: match[0]
			});

			if (match[0].length === 0) {
				regex.lastIndex++;
			}
		}

		return matches;
	}

	function findMatchesInHtmlText(value, find, caseSensitive) {
		var source = String(value || '');
		var needle = String(find || '');
		var sourceForSearch = caseSensitive ? source : source.toLowerCase();
		var needleForSearch = caseSensitive ? needle : needle.toLowerCase();
		var matches = [];
		var inTag = false;
		var quote = '';
		var segmentStart = 0;
		var i;

		if (!needleForSearch) {
			return matches;
		}

		function collectMatches(segmentFrom, segmentTo) {
			var offset = segmentFrom;
			var index;

			while (offset <= segmentTo - needleForSearch.length) {
				index = sourceForSearch.indexOf(needleForSearch, offset);

				if (index === -1 || index >= segmentTo) {
					break;
				}

				matches.push({
					start: index,
					end: index + needle.length,
					text: source.slice(index, index + needle.length)
				});

				offset = index + Math.max(needle.length, 1);
			}
		}

		for (i = 0; i < source.length; i++) {
			if (!inTag) {
				if (source.charAt(i) === '<') {
					collectMatches(segmentStart, i);
					inTag = true;
					quote = '';
				}
			} else if (quote) {
				if (source.charAt(i) === quote) {
					quote = '';
				}
			} else if (source.charAt(i) === '"' || source.charAt(i) === '\'') {
				quote = source.charAt(i);
			} else if (source.charAt(i) === '>') {
				inTag = false;
				segmentStart = i + 1;
			}
		}

		if (!inTag && segmentStart < source.length) {
			collectMatches(segmentStart, source.length);
		}

		return matches;
	}

	function findMatchesInTarget(target, find, caseSensitive) {
		if (shouldTreatTargetAsHtmlText(target.key)) {
			return findMatchesInHtmlText(target.value, find, caseSensitive);
		}

		return findMatchesInPlainText(target.value, find, caseSensitive);
	}

	function replaceAllMatchesInValue(value, matches, replacement) {
		var result = String(value || '');
		var index;

		for (index = matches.length - 1; index >= 0; index--) {
			result = result.slice(0, matches[index].start) + replacement + result.slice(matches[index].end);
		}

		return result;
	}

	function setStatus(message, isError) {
		$('#consortia-qfr-status').text(message).toggleClass('is-error', !!isError);
	}

	function setMatchSummary(message) {
		$('#consortia-qfr-match-summary').text(message);
	}

	function syncMatchNavigation() {
		var disabled = !state.matches.length;
		$('#consortia-qfr-prev, #consortia-qfr-next').prop('disabled', disabled);
	}

	function syncUndoButton() {
		$('#consortia-qfr-undo').prop('disabled', !state.undoSnapshot);
	}

	function createUndoSnapshot(targets) {
		var snapshot = {};

		if (targets.indexOf('title') !== -1) {
			snapshot.title = getTitleValue();
		}

		if (targets.indexOf('content') !== -1) {
			snapshot.content = getContentValue();
		}

		state.undoSnapshot = snapshot;
		syncUndoButton();
	}

	function undoLastChange() {
		if (!state.undoSnapshot) {
			setStatus(getI18n('nothingToUndo', 'Nothing to undo.'), true);
			return;
		}

		if (Object.prototype.hasOwnProperty.call(state.undoSnapshot, 'title')) {
			setTitleValue(state.undoSnapshot.title);
		}

		if (Object.prototype.hasOwnProperty.call(state.undoSnapshot, 'content')) {
			setContentValue(state.undoSnapshot.content);
		}

		state.undoSnapshot = null;
		syncUndoButton();
		buildMatches();
		setStatus(getI18n('undoDone', 'Undid the last change.'), false);
	}

	function getCurrentMatchLocator() {
		var match = state.matches[state.currentIndex] || null;

		if (!match) {
			return null;
		}

		return {
			target: match.target,
			start: match.start,
			index: state.currentIndex
		};
	}

	function getPreferredMatchIndex(preferredMatch) {
		var index;

		if (!preferredMatch) {
			return 0;
		}

		for (index = 0; index < state.matches.length; index++) {
			if (state.matches[index].target === preferredMatch.target && state.matches[index].start >= preferredMatch.start) {
				return index;
			}
		}

		for (index = 0; index < state.matches.length; index++) {
			if (state.matches[index].target === preferredMatch.target) {
				return index;
			}
		}

		return Math.min(preferredMatch.index || 0, state.matches.length - 1);
	}

	function buildMatches(options) {
		var find = $('#consortia-qfr-find').val();
		var caseSensitive = $('#consortia-qfr-match-case').is(':checked');
		var preferredMatch = options && options.preferredMatch ? options.preferredMatch : null;

		state.matches = [];
		state.currentIndex = -1;
		state.lastFind = find;
		state.lastCaseSensitive = caseSensitive;
		state.lastContentSource = getContentSource();

		if (!find) {
			setMatchSummary(getI18n('readyToSearch', 'Ready to search'));
			syncMatchNavigation();
			setStatus(getI18n('typeToSearch', 'Type text in “Find” to search this document.'), false);
			updateHighlights();
			return;
		}

		getReplaceTargets().forEach(function(target) {
			findMatchesInTarget(target, find, caseSensitive).forEach(function(match) {
				state.matches.push({
					target: target.key,
					start: match.start,
					end: match.end,
					text: match.text
				});
			});
		});

		if (!state.matches.length) {
			setMatchSummary(getI18n('noMatches', 'No matches found.'));
			syncMatchNavigation();
			setStatus(getI18n('noMatches', 'No matches found.'), true);
			updateHighlights();
			return;
		}

		state.currentIndex = getPreferredMatchIndex(preferredMatch);
		updateStatus();
	}

	function updateStatus() {
		var message;

		if (!state.matches.length || state.currentIndex < 0) {
			setMatchSummary(getI18n('readyToSearch', 'Ready to search'));
			syncMatchNavigation();
			setStatus(getI18n('noMatches', 'No matches found.'), true);
			return;
		}

		if (state.matches.length === 1) {
			setMatchSummary(getI18n('oneMatchFound', 'Found 1 match') + ' • 1/1');
		} else {
			setMatchSummary(
				getI18n('matchesFound', 'Found %d matches').replace('%d', String(state.matches.length)) +
				' • ' +
				String(state.currentIndex + 1) +
				'/' +
				String(state.matches.length)
			);
		}

		syncMatchNavigation();
		setStatus(getI18n('matchStatus', 'Match %1$d of %2$d')
			.replace('%1$d', String(state.currentIndex + 1))
			.replace('%2$d', String(state.matches.length)), false);
		updateHighlights();
	}

	function focusTextareaRange(textarea, start, end) {
		textarea.focus();
		if (typeof textarea.setSelectionRange === 'function') {
			textarea.setSelectionRange(start, end);
		}
	}

	function getTargetMatchVisualIndex(match) {
		var index = 0;
		var i;

		for (i = 0; i < state.matches.length; i++) {
			if (state.matches[i] === match) {
				return index;
			}

			if (state.matches[i].target === match.target) {
				index++;
			}
		}

		return 0;
	}

	function focusTinyMceMatch(editorId, target, match) {
		var editor;
		var body;
		var ranges;
		var visualIndex;
		var item;
		var range;
		var win;

		if (!window.tinymce) {
			return false;
		}

		editor = tinymce.get(editorId);
		if (!editor || !editor.getBody()) {
			return false;
		}

		body = editor.getBody();
		ranges = collectTextRanges(body, $('#consortia-qfr-find').val(), $('#consortia-qfr-match-case').is(':checked'), target);
		visualIndex = getTargetMatchVisualIndex(match);
		item = ranges[visualIndex];

		editor.focus();

		if (!item || !item.range) {
			return true;
		}

		range = item.range;
		win = body.ownerDocument.defaultView;

		if (editor.selection && typeof editor.selection.setRng === 'function') {
			editor.selection.setRng(range);
		}

		if (range.startContainer && range.startContainer.parentElement && typeof range.startContainer.parentElement.scrollIntoView === 'function') {
			range.startContainer.parentElement.scrollIntoView({
				block: 'center',
				inline: 'nearest'
			});
		} else if (win && typeof win.scrollTo === 'function') {
			win.scrollTo(0, Math.max(0, range.getBoundingClientRect().top + win.pageYOffset - 120));
		}

		return true;
	}

	function focusCurrentMatch() {
		var match;
		var source;
		var titleField;
		var textarea;
		var editor;
		var contentEditable;

		if (!state.matches.length || state.currentIndex < 0) {
			return;
		}

		match = state.matches[state.currentIndex];

		if (match.target === 'title') {
			titleField = getTitleField();
			if (titleField) {
				focusTextareaRange(titleField, match.start, match.end);
			}
			return;
		}

		source = getContentSource();
		if (source === 'textarea' || source === 'block-code') {
			textarea = source === 'block-code' ? getBlockCodeEditorTextarea() : document.getElementById('content');
			if (textarea) {
				focusTextareaRange(textarea, match.start, match.end);
			}
			return;
		}

		if (source === 'tinymce') {
			if (focusTinyMceMatch('content', 'content', match)) {
				return;
			}
		}

		if (source === 'block') {
			contentEditable = document.querySelector('.block-editor-rich-text__editable');
			if (contentEditable) {
				contentEditable.focus();
			}
		}
	}

	function ensureFreshMatches() {
		var currentFind = $('#consortia-qfr-find').val();
		var caseSensitive = $('#consortia-qfr-match-case').is(':checked');
		var preferredMatch = getCurrentMatchLocator();

		if (currentFind !== state.lastFind || caseSensitive !== state.lastCaseSensitive) {
			buildMatches({ preferredMatch: preferredMatch });
			return state.matches.length > 0;
		}

		if (!state.matches.length) {
			buildMatches();
			return state.matches.length > 0;
		}

		return true;
	}

	function gotoNext() {
		if (!ensureFreshMatches() || !state.matches.length) {
			return;
		}

		state.currentIndex = (state.currentIndex + 1) % state.matches.length;
		focusCurrentMatch();
		updateStatus();
	}

	function gotoPrev() {
		if (!ensureFreshMatches() || !state.matches.length) {
			return;
		}

		state.currentIndex = (state.currentIndex - 1 + state.matches.length) % state.matches.length;
		focusCurrentMatch();
		updateStatus();
	}

	function applySingleReplace(match, replacement) {
		var titleValue;
		var newTitle;
		var contentValue;
		var newContent;

		if (match.target === 'title') {
			titleValue = getTitleValue();
			newTitle = titleValue.slice(0, match.start) + replacement + titleValue.slice(match.end);
			setTitleValue(newTitle);
			return;
		}

		contentValue = getContentValue();
		newContent = contentValue.slice(0, match.start) + replacement + contentValue.slice(match.end);
		setContentValue(newContent);
	}

	function replaceAllInValue(value, find, replacement, caseSensitive) {
		var matches = findMatchesInPlainText(value, find, caseSensitive);

		if (!matches.length) {
			return {
				count: 0,
				value: String(value || '')
			};
		}

		return {
			count: matches.length,
			value: replaceAllMatchesInValue(value, matches, replacement)
		};
	}

	function replaceAllInTarget(targetKey, value, find, replacement, caseSensitive) {
		var matches = findMatchesInTarget({
			key: targetKey,
			value: value
		}, find, caseSensitive);

		if (!matches.length) {
			return {
				count: 0,
				value: String(value || '')
			};
		}

		return {
			count: matches.length,
			value: replaceAllMatchesInValue(value, matches, replacement)
		};
	}

	function rebuildMatchesAfterReplace(preferredMatch, statusMessage) {
		var attempts = 0;

		function run() {
			clearOverlayHighlights();
			clearCssHighlights();
			buildMatches({ preferredMatch: preferredMatch });

			if (state.matches.length || attempts >= 3) {
				scheduleHighlightUpdate();

				if (state.matches.length && state.currentIndex >= 0) {
					focusCurrentMatch();
					updateStatus();
				}

				if (statusMessage) {
					setStatus(statusMessage, false);
				}

				return;
			}

			attempts++;
			window.setTimeout(run, attempts === 1 ? 40 : 100);
		}

		run();
	}

	function replaceCurrent() {
		var replacement;
		var match;
		var nextMatch;

		if (!ensureFreshMatches() || !state.matches.length || state.currentIndex < 0) {
			setStatus(getI18n('nothingToReplace', 'Nothing to replace.'), true);
			return;
		}

		replacement = $('#consortia-qfr-replace').val();
		match = state.matches[state.currentIndex];
		nextMatch = {
			target: match.target,
			start: match.start + String(replacement).length,
			index: state.currentIndex
		};

		createUndoSnapshot([ match.target ]);
		applySingleReplace(match, replacement);
		rebuildMatchesAfterReplace(nextMatch, getI18n('replacedOne', 'Replaced 1 match.'));
	}

	function replaceAll() {
		var find = $('#consortia-qfr-find').val();
		var replacement = $('#consortia-qfr-replace').val();
		var caseSensitive = $('#consortia-qfr-match-case').is(':checked');
		var replaced = 0;
		var undoSnapshot = {};
		var newTitleValue = null;
		var newContentValue = null;
		var titleValue;
		var titleResult;
		var contentValue;
		var contentResult;

		if (!find) {
			setStatus(getI18n('typeToSearch', 'Type text in “Find” to search this document.'), true);
			return;
		}

		if ($('#consortia-qfr-also-title').is(':checked')) {
			titleValue = getTitleValue();
			titleResult = replaceAllInTarget('title', titleValue, find, replacement, caseSensitive);
			if (titleResult.count) {
				undoSnapshot.title = titleValue;
				replaced += titleResult.count;
				newTitleValue = titleResult.value;
			}
		}

		contentValue = getContentValue();
		contentResult = replaceAllInTarget('content', contentValue, find, replacement, caseSensitive);
		if (contentResult.count) {
			undoSnapshot.content = contentValue;
			replaced += contentResult.count;
			newContentValue = contentResult.value;
		}

		if (!replaced) {
			buildMatches();
			setStatus(getI18n('nothingToReplace', 'Nothing to replace.'), true);
			return;
		}

		if (newTitleValue !== null) {
			setTitleValue(newTitleValue);
		}

		if (newContentValue !== null) {
			setContentValue(newContentValue);
		}

		state.undoSnapshot = undoSnapshot;
		syncUndoButton();
		buildMatches();
		setStatus(getI18n('replacedMany', 'Replaced %d matches.').replace('%d', String(replaced)), false);
	}

	window.consortiaqfrApi = {
		buildMatches: buildMatches,
		getContentValue: getContentValue,
		getTitleValue: getTitleValue,
		setContentValue: setContentValue,
		setStatus: setStatus,
		setTitleValue: setTitleValue
	};

	$(function(){
		restoreReplaceSectionState();
		restoreScreenFindValue();
		restoreScreenReplaceValue();
		$('#consortia-qfr-find').on('input', buildMatches);
		$('#consortia-qfr-find').on('input', function() {
			storeScreenFindValue($(this).val());
		});
		$('#consortia-qfr-clear-find').on('click', function() {
			$('#consortia-qfr-find').val('').trigger('input').trigger('focus');
		});
		$('#consortia-qfr-clear-replace').on('click', function() {
			$('#consortia-qfr-replace').val('').trigger('focus');
			storeScreenReplaceValue('');
		});
		$('#consortia-qfr-replace').on('input', function() {
			storeScreenReplaceValue($(this).val());
		});
		$('#consortia-qfr-match-case, #consortia-qfr-also-title, #consortia-qfr-include-urls').on('change', buildMatches);
		$('#consortia-qfr-next').on('click', gotoNext);
		$('#consortia-qfr-prev').on('click', gotoPrev);
		$('#consortia-qfr-replace-current').on('click', replaceCurrent);
		$('#consortia-qfr-replace-all').on('click', replaceAll);
		$('#consortia-qfr-undo').on('click', undoLastChange);
		$('#consortia-qfr-replace-wrap').on('toggle', function() {
			storeReplaceOpenState(this.open);
		});
		$('#consortia-qfr-find, #consortia-qfr-replace').on('keydown', function(event) {
			if (event.key === 'Tab') {
				if (!event.shiftKey && event.currentTarget.id === 'consortia-qfr-find') {
					event.preventDefault();
					setReplaceSectionOpen(true);
					$('#consortia-qfr-replace').trigger('focus');
					return;
				}

				if (event.shiftKey && event.currentTarget.id === 'consortia-qfr-replace') {
					event.preventDefault();
					$('#consortia-qfr-find').trigger('focus');
					return;
				}
			}

			if (event.key === 'Enter') {
				event.preventDefault();
				if (event.shiftKey) {
					gotoPrev();
				} else {
					gotoNext();
				}
			}
		});
		$('#title, #content').on('input scroll', scheduleHighlightUpdate);
		$(document).on('input keyup mouseup', '.block-editor-writing-flow, .editor-post-title__input', scheduleHighlightUpdate);
		$(document).on('input keyup mouseup', 'textarea.editor-post-text-editor, .edit-post-text-editor__body textarea, .block-editor-code-editor textarea', scheduleHighlightUpdate);
		$(document).on('click', '[aria-label=\"Code editor\"], [aria-label=\"Visual editor\"], .editor-post-text-editor, .interface-complementary-area__fill button', refreshAfterEditorLayoutChange);
		document.addEventListener('scroll', scheduleHighlightUpdate, true);
		$(document).on('click', '.handle-order-higher, .handle-order-lower', function() {
			clearOverlayHighlights();
			updateHighlights();
			window.setTimeout(function() {
				buildMatches({ preferredMatch: getCurrentMatchLocator() });
			}, 50);
		});
		$(window).on('resize scroll', scheduleHighlightUpdate);
		setMatchSummary(getI18n('readyToSearch', 'Ready to search'));
		syncMatchNavigation();
		buildMatches();
		syncUndoButton();
	});
})(jQuery);
