import ko from 'ko';
import { addEventsListeners, addShortcut, registerShortcut } from 'Common/Globals';
import { isArray } from 'Common/Utils';
import { koComputable } from 'External/ko';

/*
	oCallbacks:
		ItemSelect
		MiddleClick
		AutoSelect
		ItemGetUid
		UpOrDown
*/

export class Selector {
	/**
	 * @param {koProperty} koList
	 * @param {koProperty} koSelectedItem
	 * @param {koProperty} koFocusedItem
	 * @param {string} sItemSelector
	 * @param {string} sItemCheckedSelector
	 * @param {string} sItemFocusedSelector
	 */
	constructor(
		koList,
		koSelectedItem,
		koFocusedItem,
		sItemSelector,
		sItemCheckedSelector,
		sItemFocusedSelector
	) {
		this.list = koList;
		this.listChecked = koComputable(() => this.list.filter(item => item.checked())).extend({ rateLimit: 0 });
		this.isListChecked = koComputable(() => 0 < this.listChecked().length);

		this.focusedItem = koFocusedItem || ko.observable(null);
		this.selectedItem = koSelectedItem || ko.observable(null);

		this.selectedItemUseCallback = true;

		this.iSelectNextHelper = 0;
		this.iFocusedNextHelper = 0;
		this.oContentScrollable;

		this.sItemSelector = sItemSelector;
		this.sItemCheckedSelector = sItemCheckedSelector;
		this.sItemFocusedSelector = sItemFocusedSelector;

		this.sLastUid = '';
		this.oCallbacks = {};

		const itemSelectedThrottle = (item => this.itemSelected(item)).debounce(300);

		this.listChecked.subscribe((items) => {
			if (items.length) {
				if (null === this.selectedItem()) {
					if (this.selectedItem.valueHasMutated) {
						this.selectedItem.valueHasMutated();
					}
				} else {
					this.selectedItem(null);
				}
			} else if (this.autoSelect() && this.focusedItem()) {
				this.selectedItem(this.focusedItem());
			}
		}, this);

		this.selectedItem.subscribe((item) => {
			if (item) {
				if (this.isListChecked()) {
					this.listChecked().forEach(subItem => subItem.checked(false));
				}

				if (this.selectedItemUseCallback) {
					itemSelectedThrottle(item);
				}
			} else if (this.selectedItemUseCallback) {
				this.itemSelected(null);
			}
		}, this);

		this.selectedItem = this.selectedItem.extend({ toggleSubscribeProperty: [this, 'selected'] });
		this.focusedItem = this.focusedItem.extend({ toggleSubscribeProperty: [null, 'focused'] });

		this.focusedItem.subscribe(item => item && (this.sLastUid = this.getItemUid(item)), this);

		let aCache = [],
			aCheckedCache = [],
			mFocused = null,
			mSelected = null;

		this.list.subscribe(
			items => {
				if (isArray(items)) {
					items.forEach(item => {
						if (item) {
							const uid = this.getItemUid(item);

							aCache.push(uid);
							item.checked() && aCheckedCache.push(uid);
							if (null === mFocused && item.focused()) {
								mFocused = uid;
							}
							if (null === mSelected && item.selected()) {
								mSelected = uid;
							}
						}
					});
				}
			},
			this,
			'beforeChange'
		);

		this.list.subscribe((aItems) => {
			let temp = null,
				getNext = false,
				isNextFocused = mFocused,
				isChecked = false,
				isSelected = false,
				len = 0;

			const uids = [];

			this.selectedItemUseCallback = false;

			this.focusedItem(null);
			this.selectedItem(null);

			if (isArray(aItems)) {
				len = aCheckedCache.length;

				aItems.forEach(item => {
					const uid = this.getItemUid(item);
					uids.push(uid);

					if (null !== mFocused && mFocused === uid) {
						this.focusedItem(item);
						mFocused = null;
					}

					if (0 < len && aCheckedCache.includes(uid)) {
						isChecked = true;
						item.checked(true);
						--len;
					}

					if (!isChecked && null !== mSelected && mSelected === uid) {
						isSelected = true;
						this.selectedItem(item);
						mSelected = null;
					}
				});

				this.selectedItemUseCallback = true;

				if (!isChecked && !isSelected && this.autoSelect()) {
					if (this.focusedItem()) {
						this.selectedItem(this.focusedItem());
					} else if (aItems.length) {
						if (null !== isNextFocused) {
							getNext = false;
							isNextFocused = aCache.find(sUid => {
								if (getNext && uids.includes(sUid)) {
									return sUid;
								}
								if (isNextFocused === sUid) {
									getNext = true;
								}
								return false;
							});

							if (isNextFocused) {
								temp = aItems.find(oItem => isNextFocused === this.getItemUid(oItem));
							}
						}

						this.selectedItem(temp || null);
						this.focusedItem(this.selectedItem());
					}
				}

				if (
					(0 !== this.iSelectNextHelper || 0 !== this.iFocusedNextHelper) &&
					aItems.length &&
					!this.focusedItem()
				) {
					temp = null;
					if (0 !== this.iFocusedNextHelper) {
						temp = aItems[-1 === this.iFocusedNextHelper ? aItems.length - 1 : 0] || null;
					}

					if (!temp && 0 !== this.iSelectNextHelper) {
						temp = aItems[-1 === this.iSelectNextHelper ? aItems.length - 1 : 0] || null;
					}

					if (temp) {
						if (0 !== this.iSelectNextHelper) {
							this.selectedItem(temp || null);
						}

						this.focusedItem(temp || null);

						this.scrollToFocused();

						setTimeout(this.scrollToFocused, 100);
					}

					this.iSelectNextHelper = 0;
					this.iFocusedNextHelper = 0;
				}
			}

			aCache = [];
			aCheckedCache = [];
			mFocused = null;
			mSelected = null;
		});
	}

	itemSelected(item) {
		if (this.isListChecked()) {
			item || (this.oCallbacks.ItemSelect || (()=>0))(null);
		} else if (item) {
			(this.oCallbacks.ItemSelect || (()=>0))(item);
		}
	}

	unselect() {
		this.selectedItem(null);
		this.focusedItem(null);
	}

	init(contentScrollable, keyScope = 'all') {
		this.oContentScrollable = contentScrollable;

		if (contentScrollable) {
			let getItem = selector => {
				let el = event.target.closestWithin(selector, contentScrollable);
				return el ? ko.dataFor(el) : null;
			};

			addEventsListeners(contentScrollable, {
				click: event => {
					let el = event.target.closestWithin(this.sItemSelector, contentScrollable);
					el && this.actionClick(ko.dataFor(el), event);

					const item = getItem(this.sItemCheckedSelector);
					if (item) {
						if (event.shiftKey) {
							this.actionClick(item, event);
						} else {
							this.focusedItem(item);
							item.checked(!item.checked());
						}
					}
				},
				auxclick: event => {
					if (1 == event.button) {
						const item = getItem(this.sItemSelector);
						if (item) {
							this.focusedItem(item);
							(this.oCallbacks.MiddleClick || (()=>0))(item);
						}
					}
				}
			});

			registerShortcut('enter,open', '', keyScope, () => {
				const focused = this.focusedItem();
				if (focused && !focused.selected()) {
					this.actionClick(focused);
					return false;
				}
			});

			addShortcut('arrowup,arrowdown', 'meta', keyScope, () => false);

			addShortcut('arrowup,arrowdown', 'shift', keyScope, event => {
				this.newSelectPosition(event.key, true);
				return false;
			});
			registerShortcut('arrowup,arrowdown,home,end,pageup,pagedown,space', '', keyScope, event => {
				this.newSelectPosition(event.key, false);
				return false;
			});
		}
	}

	/**
	 * @returns {boolean}
	 */
	autoSelect() {
		return !!(this.oCallbacks.AutoSelect || (()=>1))();
	}

	/**
	 * @param {Object} oItem
	 * @returns {string}
	 */
	getItemUid(item) {
		let uid = '';

		const getItemUidCallback = this.oCallbacks.ItemGetUid || null;
		if (getItemUidCallback && item) {
			uid = getItemUidCallback(item);
		}

		return uid.toString();
	}

	/**
	 * @param {string} sEventKey
	 * @param {boolean} bShiftKey
	 * @param {boolean=} bForceSelect = false
	 */
	newSelectPosition(sEventKey, bShiftKey, bForceSelect) {
		let isArrow = 'ArrowUp' === sEventKey || 'ArrowDown' === sEventKey,
			result = null;

		const pageStep = 10,
			list = this.list(),
			listLen = list.length,
			focused = this.focusedItem();

		if (listLen) {
			if (focused) {
				if (isArrow) {
					let i = list.indexOf(focused);
					if ('ArrowUp' == sEventKey) {
						i > 0 && (result = list[i-1]);
					} else if (++i < listLen) {
						result = list[i];
					}
					result || (this.oCallbacks.UpOrDown || (()=>0))('ArrowUp' === sEventKey);
				} else if ('Home' === sEventKey) {
					result = list[0];
				} else if ('End' === sEventKey) {
					result = list[list.length - 1];
				} else if ('PageDown' === sEventKey) {
					let i = list.indexOf(focused);
					if (i < listLen - 1) {
						result = list[Math.min(i + pageStep, listLen - 1)];
					}
				} else if ('PageUp' === sEventKey) {
					let i = list.indexOf(focused);
					if (i > 0) {
						result = list[Math.max(0, i - pageStep)];
					}
				}
			} else if (
				'Home' == sEventKey ||
				'PageUp' == sEventKey
			) {
				result = list[0];
			} else if (
				'End' === sEventKey ||
				'PageDown' === sEventKey
			) {
				result = list[list.length - 1];
			}
		}

		if (result) {
			this.focusedItem(result);

			if (focused && ((bShiftKey && isArrow) || ' ' === sEventKey)) {
				focused.checked(!focused.checked());
			}

			if (' ' !== sEventKey && (this.autoSelect() || bForceSelect) && !this.isListChecked()) {
				this.selectedItem(result);
			}

			this.scrollToFocused();
		} else if (focused) {
			if ((bShiftKey && isArrow) || ' ' === sEventKey) {
				focused.checked(!focused.checked());
			}

			this.focusedItem(focused);
		}
	}

	/**
	 * @returns {boolean}
	 */
	scrollToFocused() {
		const scrollable = this.oContentScrollable;
		if (scrollable) {
			let block, focused = scrollable.querySelector(this.sItemFocusedSelector);
			if (focused) {
				const fRect = focused.getBoundingClientRect(),
					sRect = scrollable.getBoundingClientRect();
				if (fRect.top < sRect.top) {
					block = 'start';
				} else if (fRect.bottom > sRect.bottom) {
					block = 'end';
				}
				block && focused.scrollIntoView(block === 'start');
			} else {
				scrollable.scrollTop = 0;
			}
		}
	}

	/**
	 * @returns {boolean}
	 */
	scrollToTop() {
		this.oContentScrollable && (this.oContentScrollable.scrollTop = 0);
	}

	eventClickFunction(item, event) {
		let index = 0,
			length = 0,
			changeRange = false,
			isInRange = false,
			list = [],
			checked = false,
			listItem = null,
			lineUid = '';

		const uid = this.getItemUid(item);
		if (event && event.shiftKey) {
			if (uid && this.sLastUid && uid !== this.sLastUid) {
				list = this.list();
				checked = item.checked();

				for (index = 0, length = list.length; index < length; index++) {
					listItem = list[index];
					lineUid = this.getItemUid(listItem);

					changeRange = false;
					if (lineUid === this.sLastUid || lineUid === uid) {
						changeRange = true;
					}

					if (changeRange) {
						isInRange = !isInRange;
					}

					if (isInRange || changeRange) {
						listItem.checked(checked);
					}
				}
			}
		}

		this.sLastUid = uid || '';
	}

	/**
	 * @param {Object} item
	 * @param {Object=} event
	 */
	actionClick(item, event = null) {
		if (item) {
			let click = true;
			if (event) {
				if (event.shiftKey && !(event.ctrlKey || event.metaKey) && !event.altKey) {
					click = false;
					if (!this.sLastUid) {
						this.sLastUid = this.getItemUid(item);
					}

					item.checked(!item.checked());
					this.eventClickFunction(item, event);

					this.focusedItem(item);
				} else if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) {
					click = false;
					this.focusedItem(item);

					if (this.selectedItem() && item !== this.selectedItem()) {
						this.selectedItem().checked(true);
					}

					item.checked(!item.checked());
				}
			}

			if (click) {
				this.selectMessageItem(item);
			}
		}
	}

	on(eventName, callback) {
		this.oCallbacks[eventName] = callback;
	}

	selectMessageItem(messageItem) {
		this.focusedItem(messageItem);
		this.selectedItem(messageItem);
		this.scrollToFocused();
	}
}
