import $ from 'jquery';
import _ from 'underscore';
import peepso from 'peepso';

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

const TRIGGER = '#';
const EVT_NS = '.ps-hashtag-dropdown';

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

let cachedSuggestions = null;
let cachedAt = 0;
const CACHE_TTL = 60 * 1000;

export function fetchSuggestions(query, callback) {
	const useCache = !query && cachedSuggestions && Date.now() - cachedAt < CACHE_TTL;
	if (useCache) {
		callback(cachedSuggestions);
		return;
	}

	peepso.postJson('hashtagsajax.get_hashtags', { query }, response => {
		const items = response && response.success ? response.data.hashtags || [] : [];
		if (!query) {
			cachedSuggestions = items;
			cachedAt = Date.now();
		}
		callback(items);
	});
}

class HashtagDropdown {
	constructor(textarea, options) {
		this.textarea = textarea;
		this.$textarea = $(textarea);
		this.options = options || {};
		this.$dropdown = null;
		this.items = [];
		this.visible = false;
		this.fetching = false;
		this.selected = null;

		this.toggle = _.debounce(this.toggleImpl.bind(this), 200);

		this.prepareDom();
		this.bind();
	}

	prepareDom() {
		this.$dropdown = $(`<div class="${CSS_DROPDOWN}" style="display:none"></div>`);

		const $container = this.options.$container && this.options.$container.length
			? this.options.$container
			: null;
		const $tagWrapper = this.$textarea.closest('.ps-postbox__input-tag');

		if ($container) {
			$container.append(this.$dropdown);
		} else if ($tagWrapper.length && $tagWrapper.parent().length) {
			// Match the usertag dropdown placement: sibling of `.pso-postbox__input`.
			this.$dropdown.insertAfter($tagWrapper.parent());
		} else if ($tagWrapper.length) {
			this.$dropdown.insertAfter($tagWrapper);
		} else {
			this.$dropdown.insertAfter(this.$textarea);
		}
	}

	bind() {
		this.$textarea
			.on('focus' + EVT_NS, () => this.toggle())
			.on('click' + EVT_NS, () => this.toggle())
			.on('keydown' + EVT_NS, e => this.onKeydown(e))
			.on('keyup' + EVT_NS, e => this.onKeyup(e))
			.on('input' + EVT_NS, () => this.toggle())
			.on('blur' + EVT_NS, () => {
				// Defer so that mousedown on a dropdown item can complete.
				setTimeout(() => {
					if (!this.clicked) this.hide();
				}, 150);
			});

		this.$dropdown
			.on('mouseenter' + EVT_NS, '.ps-tagging-dropdown-item', e => this.markActive($(e.currentTarget)))
			.on('mousedown' + EVT_NS, '.ps-tagging-dropdown-item', () => { this.clicked = true; })
			.on('mouseup' + EVT_NS, '.ps-tagging-dropdown-item', e => {
				this.selectItem($(e.currentTarget));
				this.clicked = false;
			});
	}

	onKeydown(e) {
		if (!this.visible) return;
		if ([KEY_ENTER, KEY_ESC, KEY_UP, KEY_DOWN].indexOf(e.keyCode) >= 0) {
			e.preventDefault();
			e.stopPropagation();
		}
	}

	onKeyup(e) {
		if (!this.visible) return;

		if (e.keyCode === KEY_UP || e.keyCode === KEY_DOWN) {
			this.moveActive(e.keyCode === KEY_UP ? -1 : 1);
			e.preventDefault();
			e.stopPropagation();
		} else if (e.keyCode === KEY_ENTER) {
			if (this.selected && this.selected.length) {
				this.selectItem(this.selected);
			}
			e.preventDefault();
			e.stopPropagation();
		} else if (e.keyCode === KEY_ESC) {
			this.hide();
			e.preventDefault();
			e.stopPropagation();
		}
	}

	getActiveQuery() {
		const cpos = this.textarea.selectionStart;
		const before = this.textarea.value.substr(0, cpos);
		const index = before.lastIndexOf(TRIGGER);

		if (index < 0) return null;
		// '#' must be at start of value or preceded by whitespace.
		if (index > 0 && !/\s/.test(before[index - 1])) return null;
		const candidate = before.substr(index + 1, cpos - index - 1);
		// Stop if whitespace was typed after '#'.
		if (/\s/.test(candidate)) return null;
		return { index, query: candidate };
	}

	toggleImpl() {
		const active = this.getActiveQuery();
		if (!active) {
			this.hide();
			return;
		}

		this.activeStart = active.index;

		if (this.fetching) return;
		this.fetching = true;
		fetchSuggestions(active.query, items => {
			this.fetching = false;
			// Recompute the active query — user may have typed more by now.
			const latest = this.getActiveQuery();
			if (!latest) {
				this.hide();
				return;
			}
			this.activeStart = latest.index;
			this.render(this.filterItems(items, latest.query));
		});
	}

	filterItems(items, query) {
		if (!query) return items;
		const q = query.toLowerCase();
		return items.filter(item => item.name.toLowerCase().indexOf(q) === 0);
	}

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

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

		this.$dropdown.html(html).show();
		this.visible = true;
		this.selected = this.$dropdown.children().first().addClass(CSS_ITEM_ACTIVE);
	}

	markActive($el) {
		this.$dropdown.children().removeClass(CSS_ITEM_ACTIVE);
		$el.addClass(CSS_ITEM_ACTIVE);
		this.selected = $el;
	}

	moveActive(direction) {
		const $children = this.$dropdown.children();
		if (!$children.length) return;

		let idx = $children.index(this.selected);
		idx = (idx + direction + $children.length) % $children.length;
		this.markActive($children.eq(idx));
	}

	selectItem($el) {
		const name = $el.data('name');
		if (!name || this.activeStart == null) {
			this.hide();
			return;
		}

		const cpos = this.textarea.selectionStart;
		const replacement = `#${name} `;
		const before = this.textarea.value.substr(0, this.activeStart);
		const after = this.textarea.value.substr(cpos);

		this.textarea.value = before + replacement + after;
		const newPos = (before + replacement).length;
		this.textarea.setSelectionRange(newPos, newPos);

		this.hide();
		this.$textarea.focus().trigger('input').trigger('keyup');
	}

	hide() {
		this.$dropdown.hide().empty();
		this.visible = false;
		this.selected = null;
		this.items = [];
	}
}

$.fn.ps_hashtag_dropdown = function (options) {
	return this.each(function () {
		if (!$(this).data('ps_hashtag_dropdown')) {
			$(this).data('ps_hashtag_dropdown', new HashtagDropdown(this, options || {}));
		}
	});
};

export default HashtagDropdown;
