import * as React from 'react';
import { ValidationRule, Validation, ValidationState } from '../../models/Validation';
import { validation } from '../../utils/helper';
import validationRules from '../../utils/validationRules';
import { Query } from '../../models/query';
import { Colors } from '../../models/colors';

export interface DropdownProps {
	label?: string;
	type?: string;
	name?: string;
	validationStates?: { [name: string]: Validation };
	placeholder?: string;
	validationRules?: Array<ValidationRule>;
	required?: boolean;
	options?: Array<{ label: string; id: string }>;
	optionsWithCategories?: Array<{
		category: string;
		options: Array<{ label: string; id: string }>;
	}>;
	value?: string;
	query?: Query;
	automationName?: string;
	valid?: ValidationState;
	errorMessage?: string;
	palette?: Colors;
	notifyValidity?: (name: string, state: ValidationState, errorMessage?: string) => void;
	onOptionClick?: (option: { label: string; id: string }) => void;
}

type DropdownContainer = DropdownProps & {
	render: (
		onOptionClick: (option: { label: string; id: string }) => void,
		onclick: (e: React.MouseEvent<HTMLInputElement>) => void,
		onKeyDown: (
			value: string,
			e: React.KeyboardEvent,
			option?: { label: string; id: string }
		) => void,
		value: string,
		opened: boolean,
		ref: React.RefObject<HTMLUListElement>,
		query: string,
		close: () => void
	) => JSX.Element;
};

interface DropdownState {
	value: string;
	opened: boolean;
	focusedLi: number;
}
class Dropdown extends React.Component<DropdownContainer, DropdownState> {
	private ref: React.RefObject<HTMLUListElement>;

	constructor(props: DropdownContainer) {
		super(props);
		this.state = {
			value: this.props.value,
			opened: false,
			focusedLi: -1
		};

		this.ref = React.createRef();
	}

	shouldComponentUpdate(nextProps: DropdownContainer, nextState: DropdownState) {
		return (
			this.state.value !== nextState.value ||
			this.state.opened !== nextState.opened ||
			this.props.valid !== nextProps.valid
		);
	}

	open = () => {
		if (this.state.opened) return;
		if (
			(this.props.options && this.props.options.length > 0) ||
			(this.props.optionsWithCategories && this.props.optionsWithCategories.length > 0)
		) {
			this.setState({
				opened: true
			});
			if (this.props.query !== Query.small) {
				document.addEventListener('click', this.close);
			}
		}
	};

	close = () => {
		this.setState({
			opened: false,
			focusedLi: -1
		});
		if (this.props.query !== Query.small) {
			document.removeEventListener('click', this.close);
		}
	};

	onOptionClick = (option: { label: string; id: string }) => {
		if (this.props.onOptionClick) {
			this.props.onOptionClick(option);
			let assignedValidationRules = Object.assign([], this.props.validationRules || []);

			if (this.props.required) {
				assignedValidationRules.unshift({ name: 'required' });
			}

			let { state, errorMessage } = validation(
				validationRules,
				assignedValidationRules,
				option.label,
				this.props.name
			);
			if (this.props.notifyValidity) {
				this.props.notifyValidity(this.props.name, state, errorMessage);
			}
		}

		this.setState({
			value: option.label
		});
		this.close();
	};

	onClick = (e: React.MouseEvent<HTMLInputElement>) => {
		e.preventDefault();
		e.stopPropagation();
		e.nativeEvent.stopImmediatePropagation();
		if ((this.props.options || this.props.optionsWithCategories) && !this.state.opened) {
			this.open();
		}
		if (this.state.opened) {
			this.close();
		}
	};

	onKeyDown = (value: string, e: React.KeyboardEvent, option?: { label: string; id: string }) => {
		let options: Array<{ label: string; id: string }> = [];
		if (this.props.optionsWithCategories && this.props.optionsWithCategories.length) {
			this.props.optionsWithCategories.forEach(option => {
				options = options.concat(option.options);
			});
		} else {
			options = this.props.options;
		}

		if (e.keyCode === 40) {
			//arrow down
			e.preventDefault();
			if (!this.state.opened) {
				return this.setState({
					opened: true
				});
			}
			let focusedIndex = this.state.focusedLi === options.length - 1 ? 0 : this.state.focusedLi + 1;
			Array.from(this.ref.current.querySelectorAll('li'))
				.filter(li => {
					return !li.dataset['category'];
				})
				[focusedIndex].focus();
			this.setState({
				focusedLi: focusedIndex,
				value: options[focusedIndex].label
			});
		}
		if (e.keyCode === 38) {
			//arrow up
			e.preventDefault();
			if (!this.state.opened) return;
			let focusedIndex = this.state.focusedLi === 0 ? options.length - 1 : this.state.focusedLi - 1;
			Array.from(this.ref.current.querySelectorAll('li'))
				.filter(li => {
					return !li.dataset['category'];
				})
				[focusedIndex].focus();
			this.setState({
				focusedLi: focusedIndex,
				value: options[focusedIndex].label
			});
		}

		if (e.keyCode === 13) {
			//enter
			this.onOptionClick(option);
		}
		if (e.keyCode === 9) {
			//tab
			this.close();
		}
	};

	render() {
		return this.props.render(
			this.onOptionClick,
			this.onClick,
			this.onKeyDown,
			this.state.value,
			this.state.opened,
			this.ref,
			this.props.query,
			this.close
		);
	}
}

export default Dropdown;
