<template>
	<form
		id="form"
		method="post"
		action=""
		@submit.prevent="onSubmit"
	>
		<span id="nick">{{ network.nick }}</span>
		<textarea
			id="input"
			ref="input"
			:value="channel.pendingMessage"
			:placeholder="getInputPlaceholder(channel)"
			:aria-label="getInputPlaceholder(channel)"
			class="mousetrap"
			@input="setPendingMessage"
			@keypress.enter.exact.prevent="onSubmit"
		/>
		<span
			v-if="this.$root.isFileUploadEnabled"
			id="upload-tooltip"
			class="tooltipped tooltipped-w tooltipped-no-touch"
			aria-label="Upload file"
			@click="openFileUpload"
		>
			<input
				id="upload-input"
				ref="uploadInput"
				type="file"
				multiple
			>
			<button
				id="upload"
				type="button"
				aria-label="Upload file"
				:disabled="!this.$root.isConnected"
			/>
		</span>
		<span
			id="submit-tooltip"
			class="tooltipped tooltipped-w tooltipped-no-touch"
			aria-label="Send message"
		>
			<button
				id="submit"
				type="submit"
				aria-label="Send message"
				:disabled="!this.$root.isConnected"
			/>
		</span>
	</form>
</template>

<script>
const commands = require("../js/commands/index");
const socket = require("../js/socket");
const upload = require("../js/upload");
const Mousetrap = require("mousetrap");
const {wrapCursor} = require("undate");

const formattingHotkeys = {
	k: "\x03",
	b: "\x02",
	u: "\x1F",
	i: "\x1D",
	o: "\x0F",
	s: "\x1e",
	m: "\x11",
};

// Autocomplete bracket and quote characters like in a modern IDE
// For example, select `text`, press `[` key, and it becomes `[text]`
const bracketWraps = {
	'"': '"',
	"'": "'",
	"(": ")",
	"<": ">",
	"[": "]",
	"{": "}",
	"*": "*",
	"`": "`",
	"~": "~",
	"_": "_",
};

export default {
	name: "ChatInput",
	props: {
		network: Object,
		channel: Object,
	},
	watch: {
		"channel.pendingMessage"() {
			this.setInputSize();
		},
	},
	mounted() {
		if (this.$root.settings.autocomplete) {
			require("../js/autocompletion").enable(this.$refs.input);
		}

		const inputTrap = Mousetrap(this.$refs.input);

		for (const hotkey in formattingHotkeys) {
			inputTrap.bind("mod+" + hotkey, function(e) {
				// Key is lowercased because keybinds also get processed if caps lock is on
				const modifier = formattingHotkeys[e.key.toLowerCase()];

				wrapCursor(
					e.target,
					modifier,
					e.target.selectionStart === e.target.selectionEnd ? "" : modifier
				);

				return false;
			});
		}

		inputTrap.bind(Object.keys(bracketWraps), function(e) {
			if (e.target.selectionStart !== e.target.selectionEnd) {
				wrapCursor(e.target, e.key, bracketWraps[e.key]);

				return false;
			}
		});

		inputTrap.bind(["up", "down"], (e, key) => {
			if (this.$root.isAutoCompleting || e.target.selectionStart !== e.target.selectionEnd) {
				return;
			}

			if (this.channel.inputHistoryPosition === 0) {
				this.channel.inputHistory[this.channel.inputHistoryPosition] = this.channel.pendingMessage;
			}

			if (key === "up") {
				if (this.channel.inputHistoryPosition < this.channel.inputHistory.length - 1) {
					this.channel.inputHistoryPosition++;
				}
			} else if (this.channel.inputHistoryPosition > 0) {
				this.channel.inputHistoryPosition--;
			}

			this.channel.pendingMessage = this.$refs.input.value = this.channel.inputHistory[this.channel.inputHistoryPosition];
			this.setInputSize();

			return false;
		});

		if (this.$root.isFileUploadEnabled) {
			upload.initialize();
		}
	},
	destroyed() {
		require("../js/autocompletion").disable();
		upload.abort();
	},
	methods: {
		setPendingMessage(e) {
			this.channel.pendingMessage = e.target.value;
			this.channel.inputHistoryPosition = 0;
			this.setInputSize();
		},
		setInputSize() {
			this.$nextTick(() => {
				const style = window.getComputedStyle(this.$refs.input);
				const lineHeight = parseFloat(style.lineHeight, 10) || 1;

				// Start by resetting height before computing as scrollHeight does not
				// decrease when deleting characters
				this.$refs.input.style.height = "";

				// Use scrollHeight to calculate how many lines there are in input, and ceil the value
				// because some browsers tend to incorrently round the values when using high density
				// displays or using page zoom feature
				this.$refs.input.style.height = Math.ceil(this.$refs.input.scrollHeight / lineHeight) * lineHeight + "px";
			});
		},
		getInputPlaceholder(channel) {
			if (channel.type === "channel" || channel.type === "query") {
				return `Write to ${channel.name}`;
			}

			return "";
		},
		onSubmit() {
			// Triggering click event opens the virtual keyboard on mobile
			// This can only be called from another interactive event (e.g. button click)
			this.$refs.input.click();
			this.$refs.input.focus();

			if (!this.$root.isConnected) {
				return false;
			}

			const target = this.channel.id;
			const text = this.channel.pendingMessage;

			if (text.length === 0) {
				return false;
			}

			this.channel.inputHistoryPosition = 0;
			this.channel.pendingMessage = "";
			this.$refs.input.value = "";
			this.setInputSize();

			// Store new message in history if last message isn't already equal
			if (this.channel.inputHistory[1] !== text) {
				this.channel.inputHistory.splice(1, 0, text);
			}

			// Limit input history to a 100 entries
			if (this.channel.inputHistory.length > 100) {
				this.channel.inputHistory.pop();
			}

			if (text[0] === "/") {
				const args = text.substr(1).split(" ");
				const cmd = args.shift().toLowerCase();

				if (Object.prototype.hasOwnProperty.call(commands, cmd) && commands[cmd].input(args)) {
					return false;
				}
			}

			socket.emit("input", {target, text});
		},
		openFileUpload() {
			this.$refs.uploadInput.click();
		},
	},
};
</script>