import { html, Component, createRef } from "../lib/index.js";

export default class Composer extends Component {
	state = {
		text: "",
	};
	textInput = createRef();
	lastAutocomplete = null;

	constructor(props) {
		super(props);

		this.handleInput = this.handleInput.bind(this);
		this.handleSubmit = this.handleSubmit.bind(this);
		this.handleInputKeyDown = this.handleInputKeyDown.bind(this);
		this.handleWindowKeyDown = this.handleWindowKeyDown.bind(this);
		this.handleWindowPaste = this.handleWindowPaste.bind(this);
	}

	handleInput(event) {
		this.setState({ [event.target.name]: event.target.value });

		if (this.props.readOnly && event.target.name === "text" && !event.target.value) {
			event.target.blur();
		}
	}

	handleSubmit(event) {
		event.preventDefault();
		this.props.onSubmit(this.state.text);
		this.setState({ text: "" });
	}

	handleInputKeyDown(event) {
		let input = event.target;

		if (!this.props.autocomplete || event.key !== "Tab") {
			return;
		}

		if (input.selectionStart !== input.selectionEnd) {
			return;
		}

		event.preventDefault();

		let carretPos = input.selectionStart;
		let text = this.state.text;
		let autocomplete;
		if (this.lastAutocomplete && this.lastAutocomplete.text === text && this.lastAutocomplete.carretPos === carretPos) {
			autocomplete = this.lastAutocomplete;
		} else {
			this.lastAutocomplete = null;

			let wordStart;
			for (wordStart = carretPos - 1; wordStart >= 0; wordStart--) {
				if (text[wordStart] === " ") {
					break;
				}
			}
			wordStart++;

			let wordEnd;
			for (wordEnd = carretPos; wordEnd < text.length; wordEnd++) {
				if (text[wordEnd] === " ") {
					break;
				}
			}

			let word = text.slice(wordStart, wordEnd);
			if (!word) {
				return;
			}

			let replacements = this.props.autocomplete(word);
			if (replacements.length === 0) {
				return;
			}

			autocomplete = {
				text,
				carretPos: input.selectionStart,
				prefix: text.slice(0, wordStart),
				suffix: text.slice(wordEnd),
				replacements,
				replIndex: -1,
			};
		}

		let n = autocomplete.replacements.length;
		if (event.shiftKey) {
			autocomplete.replIndex--;
		} else {
			autocomplete.replIndex++;
		}
		autocomplete.replIndex = (autocomplete.replIndex + n) % n;

		let repl = autocomplete.replacements[autocomplete.replIndex];
		if (!autocomplete.prefix && !autocomplete.suffix) {
			if (repl.startsWith("/")) {
				repl += " ";
			} else {
				repl += ": ";
			}
		}

		autocomplete.text = autocomplete.prefix + repl + autocomplete.suffix;
		autocomplete.carretPos = autocomplete.prefix.length + repl.length;

		input.value = autocomplete.text;
		input.selectionStart = autocomplete.carretPos;
		input.selectionEnd = input.selectionStart;

		this.lastAutocomplete = autocomplete;

		this.setState({ text: autocomplete.text });
	}

	handleWindowKeyDown(event) {
		// If an <input> or <button> is focused, ignore.
		if (document.activeElement !== document.body && document.activeElement.tagName !== "SECTION") {
			return;
		}

		// If a modifier is pressed, reserve for key bindings.
		if (event.altKey || event.ctrlKey || event.metaKey) {
			return;
		}

		// Ignore events that don't produce a Unicode string. If the key event
		// result in a character being typed by the user, KeyboardEvent.key
		// will contain the typed string. The key string may contain one
		// Unicode non-control character and multiple Unicode combining
		// characters. String.prototype.length cannot be used since it would
		// return the number of Unicode code-points. Instead, the spread
		// operator is used to count the number of non-combining Unicode
		// characters.
		if ([...event.key].length !== 1) {
			return;
		}

		if (this.state.text) {
			return;
		}

		if (this.props.readOnly && event.key !== "/") {
			return;
		}

		event.preventDefault();
		this.setState({ text: event.key }, () => {
			this.focus();
		});
	}

	handleWindowPaste(event) {
		// If an <input> is focused, ignore.
		if (document.activeElement !== document.body && document.activeElement.tagName !== "SECTION") {
			return;
		}

		if (this.props.readOnly) {
			return;
		}

		if (!this.textInput.current) {
			return;
		}

		let text = event.clipboardData.getData('text');

		event.preventDefault();
		event.stopImmediatePropagation();

		this.textInput.current.focus();
		this.textInput.current.setRangeText(text, undefined, undefined, "end");
		this.setState({ text: this.textInput.current.value });
	}

	componentDidMount() {
		window.addEventListener("keydown", this.handleWindowKeyDown);
		window.addEventListener("paste", this.handleWindowPaste);
	}

	componentWillUnmount() {
		window.removeEventListener("keydown", this.handleWindowKeyDown);
		window.removeEventListener("paste", this.handleWindowPaste);
	}

	focus() {
		if (!this.textInput.current) {
			return;
		}
		document.activeElement.blur(); // in case we're read-only
		this.textInput.current.focus();
	}

	render() {
		let className = "";
		if (this.props.readOnly && !this.state.text) {
			className = "read-only";
		}

		return html`
			<form
				id="composer"
				class=${className}
				onInput=${this.handleInput}
				onSubmit=${this.handleSubmit}
			>
				<input
					type="text"
					name="text"
					ref=${this.textInput}
					value=${this.state.text}
					autocomplete="off"
					placeholder="Type a message"
					enterkeyhint="send"
					onKeyDown=${this.handleInputKeyDown}
				/>
			</form>
		`;
	}
}