<template>
	<div v-if="$store.state.networks.length === 0" class="empty">
		You are not connected to any networks yet.
	</div>
	<div v-else ref="networklist">
		<div class="jump-to-input">
			<input
				ref="searchInput"
				:value="searchText"
				placeholder="Jump to..."
				type="search"
				class="search input mousetrap"
				aria-label="Search among the channel list"
				tabindex="-1"
				@input="setSearchText"
				@keydown.up="navigateResults($event, -1)"
				@keydown.down="navigateResults($event, 1)"
				@keydown.page-up="navigateResults($event, -10)"
				@keydown.page-down="navigateResults($event, 10)"
				@keydown.enter="selectResult"
				@keydown.escape="deactivateSearch"
				@focus="activateSearch"
			/>
		</div>
		<div v-if="searchText" class="jump-to-results">
			<div v-if="results.length">
				<div
					v-for="item in results"
					:key="item.channel.id"
					@mouseenter="setActiveSearchItem(item.channel)"
					@click.prevent="selectResult"
				>
					<Channel
						v-if="item.channel.type !== 'lobby'"
						:channel="item.channel"
						:network="item.network"
						:active="item.channel === activeSearchItem"
						:is-filtering="true"
					/>
					<NetworkLobby
						v-else
						:channel="item.channel"
						:network="item.network"
						:active="item.channel === activeSearchItem"
						:is-filtering="true"
					/>
				</div>
			</div>
			<div v-else class="no-results">
				No results found.
			</div>
		</div>
		<Draggable
			v-else
			:list="$store.state.networks"
			:filter="isCurrentlyInTouch"
			:prevent-on-filter="false"
			handle=".channel-list-item[data-type='lobby']"
			draggable=".network"
			ghost-class="ui-sortable-ghost"
			drag-class="ui-sortable-dragged"
			group="networks"
			class="networks"
			@change="onNetworkSort"
			@start="onDragStart"
			@end="onDragEnd"
		>
			<div
				v-for="network in $store.state.networks"
				:id="'network-' + network.uuid"
				:key="network.uuid"
				:class="{
					collapsed: network.isCollapsed,
					'not-connected': !network.status.connected,
					'not-secure': !network.status.secure,
				}"
				class="network"
				role="region"
			>
				<NetworkLobby
					:network="network"
					:is-join-channel-shown="network.isJoinChannelShown"
					:active="
						$store.state.activeChannel &&
							network.channels[0] === $store.state.activeChannel.channel
					"
					@toggleJoinChannel="network.isJoinChannelShown = !network.isJoinChannelShown"
				/>
				<JoinChannel
					v-if="network.isJoinChannelShown"
					:network="network"
					:channel="network.channels[0]"
					@toggleJoinChannel="network.isJoinChannelShown = !network.isJoinChannelShown"
				/>

				<Draggable
					draggable=".channel-list-item"
					ghost-class="ui-sortable-ghost"
					drag-class="ui-sortable-dragged"
					:group="network.uuid"
					:filter="isCurrentlyInTouch"
					:prevent-on-filter="false"
					:list="network.channels"
					class="channels"
					@change="onChannelSort"
					@start="onDragStart"
					@end="onDragEnd"
				>
					<Channel
						v-for="(channel, index) in network.channels"
						v-if="index > 0"
						:key="channel.id"
						:channel="channel"
						:network="network"
						:active="
							$store.state.activeChannel &&
								channel === $store.state.activeChannel.channel
						"
					/>
				</Draggable>
			</div>
		</Draggable>
	</div>
</template>

<style>
.jump-to-input {
	margin: 8px;
	position: relative;
}

.jump-to-input .input {
	margin: 0;
	width: 100%;
	border: 0;
	color: #fff;
	background-color: rgba(255, 255, 255, 0.1);
	padding-right: 35px;
}

.jump-to-input .input::placeholder {
	color: rgba(255, 255, 255, 0.35);
}

.jump-to-input::before {
	content: "\f002"; /* http://fontawesome.io/icon/search/ */
	color: rgba(255, 255, 255, 0.35);
	position: absolute;
	right: 8px;
	top: 0;
	bottom: 0;
	pointer-events: none;
	line-height: 35px !important;
}

.jump-to-results {
	margin: 0;
	padding: 0;
	list-style: none;
	overflow: auto;
}

.jump-to-results .no-results {
	margin: 14px 8px;
	text-align: center;
}

.jump-to-results .channel-list-item.active {
	cursor: pointer;
}

.jump-to-results .channel-list-item .add-channel,
.jump-to-results .channel-list-item .close-tooltip {
	display: none;
}

.jump-to-results .channel-list-item[data-type="lobby"] {
	padding: 8px 14px;
}

.jump-to-results .channel-list-item[data-type="lobby"]::before {
	content: "\f233";
}
</style>

<script>
import Mousetrap from "mousetrap";
import Draggable from "vuedraggable";
import {filter as fuzzyFilter} from "fuzzy";
import NetworkLobby from "./NetworkLobby.vue";
import Channel from "./Channel.vue";
import JoinChannel from "./JoinChannel.vue";

import socket from "../js/socket";
import collapseNetwork from "../js/helpers/collapseNetwork";

export default {
	name: "NetworkList",
	components: {
		JoinChannel,
		NetworkLobby,
		Channel,
		Draggable,
	},
	data() {
		return {
			searchText: "",
			activeSearchItem: null,
		};
	},
	computed: {
		items() {
			const items = [];

			for (const network of this.$store.state.networks) {
				for (const channel of network.channels) {
					if (
						this.$store.state.activeChannel &&
						channel === this.$store.state.activeChannel.channel
					) {
						continue;
					}

					items.push({network, channel});
				}
			}

			return items;
		},
		results() {
			const results = fuzzyFilter(this.searchText, this.items, {
				extract: (item) => item.channel.name,
			}).map((item) => item.original);

			return results;
		},
	},
	watch: {
		searchText() {
			this.setActiveSearchItem();
		},
	},
	mounted() {
		Mousetrap.bind("alt+shift+right", this.expandNetwork);
		Mousetrap.bind("alt+shift+left", this.collapseNetwork);
		Mousetrap.bind("alt+j", this.toggleSearch);
	},
	beforeDestroy() {
		Mousetrap.unbind("alt+shift+right", this.expandNetwork);
		Mousetrap.unbind("alt+shift+left", this.collapseNetwork);
		Mousetrap.unbind("alt+j", this.toggleSearch);
	},
	methods: {
		expandNetwork() {
			if (this.$store.state.activeChannel) {
				collapseNetwork(this.$store.state.activeChannel.network, false);
			}
		},
		collapseNetwork() {
			if (this.$store.state.activeChannel) {
				collapseNetwork(this.$store.state.activeChannel.network, true);
			}
		},
		isCurrentlyInTouch(e) {
			// TODO: Implement a way to sort on touch devices
			return e.pointerType !== "mouse";
		},
		onDragStart(e) {
			e.target.classList.add("ui-sortable-active");
		},
		onDragEnd(e) {
			e.target.classList.remove("ui-sortable-active");
		},
		onNetworkSort(e) {
			if (!e.moved) {
				return;
			}

			socket.emit("sort", {
				type: "networks",
				order: this.$store.state.networks.map((n) => n.uuid),
			});
		},
		onChannelSort(e) {
			if (!e.moved) {
				return;
			}

			const channel = this.$store.getters.findChannel(e.moved.element.id);

			if (!channel) {
				return;
			}

			socket.emit("sort", {
				type: "channels",
				target: channel.network.uuid,
				order: channel.network.channels.map((c) => c.id),
			});
		},
		toggleSearch(event) {
			// Do not handle this keybind in the chat input because
			// it can be used to type letters with umlauts
			if (event.target.tagName === "TEXTAREA") {
				return true;
			}

			if (this.$refs.searchInput === document.activeElement) {
				this.deactivateSearch();
				return false;
			}

			this.activateSearch();
			return false;
		},
		activateSearch() {
			if (this.$refs.searchInput === document.activeElement) {
				return;
			}

			this.sidebarWasClosed = this.$store.state.sidebarOpen ? false : true;
			this.$store.commit("sidebarOpen", true);
			this.$nextTick(() => {
				this.$refs.searchInput.focus();
			});
		},
		deactivateSearch() {
			this.activeSearchItem = null;
			this.searchText = "";
			this.$refs.searchInput.blur();

			if (this.sidebarWasClosed) {
				this.$store.commit("sidebarOpen", false);
			}
		},
		setSearchText(e) {
			this.searchText = e.target.value;
		},
		setActiveSearchItem(channel) {
			if (!this.results.length) {
				return;
			}

			if (!channel) {
				channel = this.results[0].channel;
			}

			this.activeSearchItem = channel;
		},
		selectResult() {
			if (!this.searchText || !this.results.length) {
				return;
			}

			this.$root.switchToChannel(this.activeSearchItem);
			this.deactivateSearch();
			this.scrollToActive();
		},
		navigateResults(event, direction) {
			// Prevent propagation to stop global keybind handler from capturing pagedown/pageup
			// and redirecting it to the message list container for scrolling
			event.stopImmediatePropagation();
			event.preventDefault();

			if (!this.searchText) {
				return;
			}

			const channels = this.results.map((r) => r.channel);

			// Bail out if there's no channels to select
			if (!channels.length) {
				this.activeSearchItem = null;
				return;
			}

			let currentIndex = channels.indexOf(this.activeSearchItem);

			// If there's no active channel select the first or last one depending on direction
			if (!this.activeSearchItem || currentIndex === -1) {
				this.activeSearchItem = direction ? channels[0] : channels[channels.length - 1];
				this.scrollToActive();
				return;
			}

			currentIndex += direction;

			// Wrap around the list if necessary. Normaly each loop iterates once at most,
			// but might iterate more often if pgup or pgdown are used in a very short list
			while (currentIndex < 0) {
				currentIndex += channels.length;
			}

			while (currentIndex > channels.length - 1) {
				currentIndex -= channels.length;
			}

			this.activeSearchItem = channels[currentIndex];
			this.scrollToActive();
		},
		scrollToActive() {
			// Scroll the list if needed after the active class is applied
			this.$nextTick(() => {
				const el = this.$refs.networklist.querySelector(".channel-list-item.active");

				if (el) {
					el.scrollIntoView({block: "nearest", inline: "nearest"});
				}
			});
		},
	},
};
</script>