<template>
<div class="omfetrab" :class="['w' + width, 'h' + height, { big }]">
	<input ref="search" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" v-model.trim="q" :placeholder="$ts.search" @paste.stop="paste" @keyup.enter="done()">
	<div class="emojis" ref="emojis">
		<section class="result">
			<div v-if="searchResultCustom.length > 0">
				<button v-for="emoji in searchResultCustom"
					class="_button"
					:title="emoji.name"
					@click="chosen(emoji, $event)"
					:key="emoji"
					tabindex="0"
				>
					<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/>
					<img v-else :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
				</button>
			</div>
			<div v-if="searchResultUnicode.length > 0">
				<button v-for="emoji in searchResultUnicode"
					class="_button"
					:title="emoji.name"
					@click="chosen(emoji, $event)"
					:key="emoji.name"
					tabindex="0"
				>
					<MkEmoji :emoji="emoji.char"/>
				</button>
			</div>
		</section>

		<div class="index" v-if="tab === 'index'">
			<section v-if="showPinned">
				<div>
					<button v-for="emoji in pinned"
						class="_button"
						@click="chosen(emoji, $event)"
						tabindex="0"
						:key="emoji"
					>
						<MkEmoji :emoji="emoji" :normal="true"/>
					</button>
				</div>
			</section>

			<section>
				<header class="_acrylic"><i class="far fa-clock fa-fw"></i> {{ $ts.recentUsed }}</header>
				<div>
					<button v-for="emoji in $store.state.recentlyUsedEmojis"
						class="_button"
						@click="chosen(emoji, $event)"
						:key="emoji"
					>
						<MkEmoji :emoji="emoji" :normal="true"/>
					</button>
				</div>
			</section>
		</div>
		<div>
			<header class="_acrylic">{{ $ts.customEmojis }}</header>
			<XSection v-for="category in customEmojiCategories" :key="'custom:' + category" :initial-shown="false" :emojis="customEmojis.filter(e => e.category === category).map(e => ':' + e.name + ':')">{{ category || $ts.other }}</XSection>
		</div>
		<div>
			<header class="_acrylic">{{ $ts.emoji }}</header>
			<XSection v-for="category in categories" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)">{{ category }}</XSection>
		</div>
	</div>
	<div class="tabs">
		<button class="_button tab" :class="{ active: tab === 'index' }" @click="tab = 'index'"><i class="fas fa-asterisk fa-fw"></i></button>
		<button class="_button tab" :class="{ active: tab === 'custom' }" @click="tab = 'custom'"><i class="fas fa-laugh fa-fw"></i></button>
		<button class="_button tab" :class="{ active: tab === 'unicode' }" @click="tab = 'unicode'"><i class="fas fa-leaf fa-fw"></i></button>
		<button class="_button tab" :class="{ active: tab === 'tags' }" @click="tab = 'tags'"><i class="fas fa-hashtag fa-fw"></i></button>
	</div>
</div>
</template>

<script lang="ts">
import { defineComponent, markRaw } from 'vue';
import { emojilist } from '@/misc/emojilist';
import { getStaticImageUrl } from '@client/scripts/get-static-image-url';
import Particle from '@client/components/particle.vue';
import * as os from '@client/os';
import { isDeviceTouch } from '@client/scripts/is-device-touch';
import { isMobile } from '@client/scripts/is-mobile';
import { emojiCategories } from '@client/instance';
import XSection from './emoji-picker.section.vue';

export default defineComponent({
	components: {
		XSection
	},

	props: {
		showPinned: {
			required: false,
			default: true
		},
		asReactionPicker: {
			required: false
		},
	},

	emits: ['chosen'],

	data() {
		return {
			emojilist: markRaw(emojilist),
			getStaticImageUrl,
			pinned: this.$store.reactiveState.reactions,
			width: this.asReactionPicker ? this.$store.state.reactionPickerWidth : 3,
			height: this.asReactionPicker ? this.$store.state.reactionPickerHeight : 2,
			big: this.asReactionPicker ? isDeviceTouch : false,
			customEmojiCategories: emojiCategories,
			customEmojis: this.$instance.emojis,
			q: null,
			searchResultCustom: [],
			searchResultUnicode: [],
			tab: 'index',
			categories: ['face', 'people', 'animals_and_nature', 'food_and_drink', 'activity', 'travel_and_places', 'objects', 'symbols', 'flags'],
		};
	},

	watch: {
		q() {
			this.$refs.emojis.scrollTop = 0;

			if (this.q == null || this.q === '') {
				this.searchResultCustom = [];
				this.searchResultUnicode = [];
				return;
			}

			const q = this.q.replace(/:/g, '');

			const searchCustom = () => {
				const max = 8;
				const emojis = this.customEmojis;
				const matches = new Set();

				const exactMatch = emojis.find(e => e.name === q);
				if (exactMatch) matches.add(exactMatch);

				if (q.includes(' ')) { // AND検索
					const keywords = q.split(' ');

					// 名前にキーワードが含まれている
					for (const emoji of emojis) {
						if (keywords.every(keyword => emoji.name.includes(keyword))) {
							matches.add(emoji);
							if (matches.size >= max) break;
						}
					}
					if (matches.size >= max) return matches;

					// 名前またはエイリアスにキーワードが含まれている
					for (const emoji of emojis) {
						if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.aliases.some(alias => alias.includes(keyword)))) {
							matches.add(emoji);
							if (matches.size >= max) break;
						}
					}
				} else {
					for (const emoji of emojis) {
						if (emoji.name.startsWith(q)) {
							matches.add(emoji);
							if (matches.size >= max) break;
						}
					}
					if (matches.size >= max) return matches;

					for (const emoji of emojis) {
						if (emoji.aliases.some(alias => alias.startsWith(q))) {
							matches.add(emoji);
							if (matches.size >= max) break;
						}
					}
					if (matches.size >= max) return matches;

					for (const emoji of emojis) {
						if (emoji.name.includes(q)) {
							matches.add(emoji);
							if (matches.size >= max) break;
						}
					}
					if (matches.size >= max) return matches;

					for (const emoji of emojis) {
						if (emoji.aliases.some(alias => alias.includes(q))) {
							matches.add(emoji);
							if (matches.size >= max) break;
						}
					}
				}

				return matches;
			};

			const searchUnicode = () => {
				const max = 8;
				const emojis = this.emojilist;
				const matches = new Set();

				const exactMatch = emojis.find(e => e.name === q);
				if (exactMatch) matches.add(exactMatch);

				if (q.includes(' ')) { // AND検索
					const keywords = q.split(' ');

					// 名前にキーワードが含まれている
					for (const emoji of emojis) {
						if (keywords.every(keyword => emoji.name.includes(keyword))) {
							matches.add(emoji);
							if (matches.size >= max) break;
						}
					}
					if (matches.size >= max) return matches;

					// 名前またはエイリアスにキーワードが含まれている
					for (const emoji of emojis) {
						if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.keywords.some(alias => alias.includes(keyword)))) {
							matches.add(emoji);
							if (matches.size >= max) break;
						}
					}
				} else {
					for (const emoji of emojis) {
						if (emoji.name.startsWith(q)) {
							matches.add(emoji);
							if (matches.size >= max) break;
						}
					}
					if (matches.size >= max) return matches;

					for (const emoji of emojis) {
						if (emoji.keywords.some(keyword => keyword.startsWith(q))) {
							matches.add(emoji);
							if (matches.size >= max) break;
						}
					}
					if (matches.size >= max) return matches;

					for (const emoji of emojis) {
						if (emoji.name.includes(q)) {
							matches.add(emoji);
							if (matches.size >= max) break;
						}
					}
					if (matches.size >= max) return matches;

					for (const emoji of emojis) {
						if (emoji.keywords.some(keyword => keyword.includes(q))) {
							matches.add(emoji);
							if (matches.size >= max) break;
						}
					}
				}

				return matches;
			};

			this.searchResultCustom = Array.from(searchCustom());
			this.searchResultUnicode = Array.from(searchUnicode());
		}
	},

	mounted() {
		this.focus();
	},

	methods: {
		focus() {
			if (!isMobile && !isDeviceTouch) {
				this.$refs.search.focus({
					preventScroll: true
				});
			}
		},

		reset() {
			this.$refs.emojis.scrollTop = 0;
			this.q = '';
		},

		getKey(emoji: any) {
			return typeof emoji === 'string' ? emoji : (emoji.char || `:${emoji.name}:`);
		},

		chosen(emoji: any, ev) {
			if (ev) {
				const el = ev.currentTarget || ev.target;
				const rect = el.getBoundingClientRect();
				const x = rect.left + (el.clientWidth / 2);
				const y = rect.top + (el.clientHeight / 2);
				os.popup(Particle, { x, y }, {}, 'end');
			}

			const key = this.getKey(emoji);
			this.$emit('chosen', key);

			// 最近使った絵文字更新
			if (!this.pinned.includes(key)) {
				let recents = this.$store.state.recentlyUsedEmojis;
				recents = recents.filter((e: any) => e !== key);
				recents.unshift(key);
				this.$store.set('recentlyUsedEmojis', recents.splice(0, 32));
			}
		},

		paste(event) {
			const paste = (event.clipboardData || window.clipboardData).getData('text');
			if (this.done(paste)) {
				event.preventDefault();
			}
		},

		done(query) {
			if (query == null) query = this.q;
			if (query == null) return;
			const q = query.replace(/:/g, '');
			const exactMatchCustom = this.customEmojis.find(e => e.name === q);
			if (exactMatchCustom) {
				this.chosen(exactMatchCustom);
				return true;
			}
			const exactMatchUnicode = this.emojilist.find(e => e.char === q || e.name === q);
			if (exactMatchUnicode) {
				this.chosen(exactMatchUnicode);
				return true;
			}
			if (this.searchResultCustom.length > 0) {
				this.chosen(this.searchResultCustom[0]);
				return true;
			}
			if (this.searchResultUnicode.length > 0) {
				this.chosen(this.searchResultUnicode[0]);
				return true;
			}
		},
	}
});
</script>

<style lang="scss" scoped>
.omfetrab {
	$pad: 8px;
	--eachSize: 40px;

	display: flex;
	flex-direction: column;

	&.big {
		--eachSize: 44px;
	}

	&.w1 {
		width: calc((var(--eachSize) * 5) + (#{$pad} * 2));
	}

	&.w2 {
		width: calc((var(--eachSize) * 6) + (#{$pad} * 2));
	}

	&.w3 {
		width: calc((var(--eachSize) * 7) + (#{$pad} * 2));
	}

	&.h1 {
		--height: calc((var(--eachSize) * 4) + (#{$pad} * 2));
	}

	&.h2 {
		--height: calc((var(--eachSize) * 6) + (#{$pad} * 2));
	}

	&.h3 {
		--height: calc((var(--eachSize) * 8) + (#{$pad} * 2));
	}

	> .search {
		width: 100%;
		padding: 12px;
		box-sizing: border-box;
		font-size: 1em;
		outline: none;
		border: none;
		background: transparent;
		color: var(--fg);

		&:not(.filled) {
			order: 1;
			z-index: 2;
			box-shadow: 0px -1px 0 0px var(--divider);
		}
	}

	> .tabs {
		display: flex;
		display: none;

		> .tab {
			flex: 1;
			height: 38px;
			border-top: solid 0.5px var(--divider);

			&.active {
				border-top: solid 1px var(--accent);
				color: var(--accent);
			}
		}
	}

	> .emojis {
		height: var(--height);
		overflow-y: auto;
		overflow-x: hidden;

		scrollbar-width: none;

		&::-webkit-scrollbar {
			display: none;
		}

		> div {
			&:not(.index) {
				padding: 4px 0 8px 0;
				border-top: solid 0.5px var(--divider);
			}

			> header {
				/*position: sticky;
				top: 0;
				left: 0;*/
				height: 32px;
				line-height: 32px;
				z-index: 2;
				padding: 0 8px;
				font-size: 12px;
			}
		}

		::v-deep(section) {
			> header {
				position: sticky;
				top: 0;
				left: 0;
				height: 32px;
				line-height: 32px;
				z-index: 1;
				padding: 0 8px;
				font-size: 12px;
				cursor: pointer;

				&:hover {
					color: var(--accent);
				}
			}

			> div {
				position: relative;
				padding: $pad;

				> button {
					position: relative;
					padding: 0;
					width: var(--eachSize);
					height: var(--eachSize);
					border-radius: 4px;

					&:focus-visible {
						outline: solid 2px var(--focus);
						z-index: 1;
					}

					&:hover {
						background: rgba(0, 0, 0, 0.05);
					}

					&:active {
						background: var(--accent);
						box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15);
					}

					> * {
						font-size: 24px;
						height: 1.25em;
						vertical-align: -.25em;
						pointer-events: none;
					}
				}
			}

			&.result {
				border-bottom: solid 0.5px var(--divider);

				&:empty {
					display: none;
				}
			}
		}
	}
}
</style>
