import $ from 'jquery';
import { observer } from 'peepso';
import { fetchSuggestions } from './dropdown';

const KEY_ENTER = 13;
const KEY_ESC = 27;
const KEY_UP = 38;
const KEY_DOWN = 40;

const TRIGGER = '#';

const CSS_DROPDOWN = 'ps-hashtag-dropdown ps-tagging-dropdown ps-post__background-selector';
const CSS_ITEM = 'ps-tagging-dropdown-item ps-hashtag-dropdown-item';
const CSS_ITEM_ACTIVE = 'active';

let $dropdown;
let activeContext = null;

function ensureDropdown() {
	if (!$dropdown) {
		$dropdown = $(`<div class="${CSS_DROPDOWN}" style="display:none;position:absolute;"></div>`);
		$dropdown.appendTo(document.body);

		$dropdown.on('mousedown', '.ps-tagging-dropdown-item', e => {
			e.preventDefault();
			e.stopPropagation();
		});
		$dropdown.on('mouseup', '.ps-tagging-dropdown-item', e => {
			e.preventDefault();
			e.stopPropagation();
			selectItem($(e.currentTarget));
		});
		$dropdown.on('mouseenter', '.ps-tagging-dropdown-item', e => {
			markActive($(e.currentTarget));
		});
	}
	return $dropdown;
}

function positionDropdown(range) {
	const rect = range.getClientRects()[0];
	if (!rect) return;

	const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
	const scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;

	$dropdown.css({
		top: rect.top + scrollTop + 25,
		left: rect.left + scrollLeft
	});
}

function render(items) {
	if (!items.length) {
		hide();
		return;
	}

	const html = items
		.map((item, index) => {
			const name = $('<span/>').text(item.name).html();
			const count = item.count
				? `<span class="ps-hashtag-dropdown-item__count">${item.count}</span>`
				: '';
			const activeCls = index === 0 ? ` ${CSS_ITEM_ACTIVE}` : '';
			return `<div class="${CSS_ITEM}${activeCls}" data-name="${name}">
				<a href="#" onclick="return false;">
					<span>#${name}</span>${count}
				</a>
			</div>`;
		})
		.join('');

	$dropdown.html(html).show();
}

function markActive($el) {
	$dropdown.children().removeClass(CSS_ITEM_ACTIVE);
	$el.addClass(CSS_ITEM_ACTIVE);
}

function move(direction) {
	const $children = $dropdown.children();
	if (!$children.length) return;

	let $active = $children.filter('.' + CSS_ITEM_ACTIVE);
	let idx = $children.index($active);
	if (idx < 0) idx = 0;
	idx = (idx + direction + $children.length) % $children.length;
	markActive($children.eq(idx));
}

function selectItem($el) {
	if (!activeContext) {
		hide();
		return;
	}

	const name = $el.data('name');
	if (!name) {
		hide();
		return;
	}

	const { textNode, triggerOffset, caretOffset, elem, editor } = activeContext;

	if (!textNode.parentNode) {
		hide();
		return;
	}

	const text = textNode.textContent;
	const before = text.substr(0, triggerOffset);
	const after = text.substr(caretOffset);
	const replacement = `#${name} `;

	textNode.textContent = before + replacement + after;

	// Move caret to right after the inserted hashtag (and space).
	const newPos = (before + replacement).length;
	const selection = window.getSelection();
	const range = document.createRange();
	range.setStart(textNode, newPos);
	range.collapse(true);
	selection.removeAllRanges();
	selection.addRange(range);

	hide();

	// Re-run normalize so the host editor picks up the change (syncs to textarea, etc).
	if (editor && typeof editor.normalize === 'function') {
		editor.normalize();
	}
	if (editor && typeof editor.opts === 'object' && typeof editor.opts.onChange === 'function') {
		editor.opts.onChange(editor);
	}

	// Refocus the editor element.
	if (elem && typeof elem.focus === 'function') {
		elem.focus();
	}
}

function hide() {
	if ($dropdown) {
		$dropdown.hide().empty();
	}
	if (activeContext && activeContext.elem) {
		$(activeContext.elem).off('keydown.ps-hashtag-ce');
		if (activeContext.editor) {
			activeContext.editor.disableInput = false;
		}
	}
	activeContext = null;
}

function bindKeys(elem, editor) {
	$(elem).off('keydown.ps-hashtag-ce');
	$(elem).on('keydown.ps-hashtag-ce', e => {
		if (!$dropdown || !$dropdown.is(':visible')) return;

		const key = e.keyCode;
		if (key === KEY_ENTER) {
			e.preventDefault();
			e.stopPropagation();
			const $active = $dropdown.find('.' + CSS_ITEM_ACTIVE);
			if ($active.length) {
				selectItem($active);
			}
		} else if (key === KEY_ESC) {
			e.preventDefault();
			e.stopPropagation();
			hide();
		} else if (key === KEY_UP) {
			e.preventDefault();
			e.stopPropagation();
			move(-1);
		} else if (key === KEY_DOWN) {
			e.preventDefault();
			e.stopPropagation();
			move(1);
		}
	});
}

/**
 * Inspect caret position. If it sits inside a `#word` (no whitespace between
 * `#` and the caret), return its position; otherwise return null.
 */
function getActiveHashtag() {
	const selection = window.getSelection();
	if (!selection.rangeCount) return null;

	const range = selection.getRangeAt(0);
	if (!range.collapsed) return null;

	const container = range.startContainer;
	if (container.nodeType !== Node.TEXT_NODE) return null;

	const caretOffset = range.startOffset;
	const before = container.textContent.substr(0, caretOffset);
	const triggerOffset = before.lastIndexOf(TRIGGER);

	if (triggerOffset < 0) return null;
	// '#' must be at start of node, preceded by whitespace, or preceded by
	// a tag boundary (start of text node = previous sibling is element).
	if (triggerOffset > 0 && !/\s/.test(before[triggerOffset - 1])) return null;

	const candidate = before.substr(triggerOffset + 1, caretOffset - triggerOffset - 1);
	// Stop if whitespace was typed after '#'.
	if (/\s/.test(candidate)) return null;

	return {
		textNode: container,
		triggerOffset,
		caretOffset,
		query: candidate,
		range
	};
}

observer.addAction(
	'postbox_content_change',
	(elem, editor) => {
		const active = getActiveHashtag();
		if (!active) {
			hide();
			return;
		}

		ensureDropdown();
		positionDropdown(active.range);
		bindKeys(elem, editor);

		activeContext = {
			textNode: active.textNode,
			triggerOffset: active.triggerOffset,
			caretOffset: active.caretOffset,
			elem,
			editor
		};

		fetchSuggestions(active.query, items => {
			// Re-check the caret context — user may have typed more by now.
			const latest = getActiveHashtag();
			if (!latest) {
				hide();
				return;
			}

			activeContext.textNode = latest.textNode;
			activeContext.triggerOffset = latest.triggerOffset;
			activeContext.caretOffset = latest.caretOffset;

			const q = latest.query.toLowerCase();
			const filtered = q
				? items.filter(item => item.name.toLowerCase().indexOf(q) === 0)
				: items;

			positionDropdown(latest.range);
			render(filtered);
		});
	},
	10,
	2
);
