<template>
	<FocusTrap v-bind:active="isActive">
		<div
			class="omfetrab"
			:class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]"
			:style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }"
			tabindex="-1"
		>
			<input
				ref="search"
				v-model.trim="q"
				class="search"
				data-prevent-emoji-insert
				:class="{ filled: q != null && q != '' }"
				:placeholder="i18n.ts.search"
				type="search"
				@paste.stop="paste"
				@keyup.enter="done()"
			/>
			<div ref="emojis" class="emojis">
				<section class="result">
					<div v-if="searchResultCustom.length > 0" class="body">
						<button
							v-for="emoji in searchResultCustom"
							:key="emoji.id"
							class="_button item"
							:title="emoji.name"
							tabindex="0"
							@click="chosen(emoji, $event)"
						>
							<!--<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/>-->
							<img
								class="emoji"
								:src="
									disableShowingAnimatedImages
										? getStaticImageUrl(emoji.url)
										: emoji.url
								"
							/>
						</button>
					</div>
					<div v-if="searchResultUnicode.length > 0" class="body">
						<button
							v-for="emoji in searchResultUnicode"
							:key="emoji.slug"
							class="_button item"
							:title="emoji.slug"
							tabindex="0"
							@click="chosen(emoji, $event)"
						>
							<MkEmoji class="emoji" :emoji="emoji.emoji" />
						</button>
					</div>
				</section>

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

					<section>
						<header class="_acrylic">
							<i class="ph-alarm ph-bold ph-fw ph-lg"></i>
							{{ i18n.ts.recentUsed }}
						</header>
						<div class="body">
							<button
								v-for="emoji in recentlyUsedEmojis"
								:key="emoji"
								class="_button item"
								@click="chosen(emoji, $event)"
							>
								<MkEmoji
									class="emoji"
									:emoji="emoji"
									:normal="true"
								/>
							</button>
						</div>
					</section>
				</div>
				<div v-once class="group">
					<header>{{ i18n.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 + ':')
						"
						@chosen="chosen"
						>{{ category || i18n.ts.other }}</XSection
					>
				</div>
				<div v-once class="group">
					<header>{{ i18n.ts.emoji }}</header>
					<XSection
						v-for="category in unicodeEmojiCategories"
						:key="category"
						:skin-tone-selector="category === 'people'"
						:skin-tones="unicodeEmojiSkinTones"
						:skin-tone-labels="unicodeEmojiSkinToneLabels"
						:emojis="
							emojilist
								.filter((e) => e.category === category)
								.map((e) => e.emoji)
						"
						@chosen="chosen"
						>{{
							getNicelyLabeledCategory(category) || category
						}}</XSection
					>
				</div>
			</div>
			<div class="tabs">
				<button
					class="_button tab"
					:class="{ active: tab === 'index' }"
					@click="tab = 'index'"
				>
					<i class="ph-asterisk ph-bold ph-lg ph-fw ph-lg"></i>
				</button>
				<button
					class="_button tab"
					:class="{ active: tab === 'custom' }"
					@click="tab = 'custom'"
				>
					<i class="ph-smiley ph-bold ph-lg ph-fw ph-lg"></i>
				</button>
				<button
					class="_button tab"
					:class="{ active: tab === 'unicode' }"
					@click="tab = 'unicode'"
				>
					<i class="ph-leaf ph-bold ph-lg ph-fw ph-lg"></i>
				</button>
				<button
					class="_button tab"
					:class="{ active: tab === 'tags' }"
					@click="tab = 'tags'"
				>
					<i class="ph-hash ph-bold ph-lg ph-fw ph-lg"></i>
				</button>
			</div>
		</div>
	</FocusTrap>
</template>

<script lang="ts" setup>
import { ref, computed, watch, onMounted } from "vue";
import * as Misskey from "iceshrimp-sdk";
import XSection from "@/components/MkEmojiPicker.section.vue";
import {
	emojilist,
	unicodeEmojiCategories,
	UnicodeEmojiDef,
	getNicelyLabeledCategory,
} from "@/scripts/emojilist";
import { getStaticImageUrl } from "@/scripts/get-static-image-url";
import Ripple from "@/components/MkRipple.vue";
import * as os from "@/os";
import { isTouchUsing } from "@/scripts/touch";
import { deviceKind } from "@/scripts/device-kind";
import { emojiCategories, instance } from "@/instance";
import { i18n } from "@/i18n";
import { defaultStore } from "@/store";
import { FocusTrap } from "focus-trap-vue";

const props = withDefaults(
	defineProps<{
		showPinned?: boolean;
		asReactionPicker?: boolean;
		maxHeight?: number;
		asDrawer?: boolean;
	}>(),
	{
		showPinned: true,
	},
);

const emit = defineEmits<{
	(ev: "chosen", v: string, ev: MouseEvent): void;
}>();

const search = ref<HTMLInputElement>();
const emojis = ref<HTMLDivElement>();

const {
	reactions: pinned,
	reactionPickerSize,
	reactionPickerWidth,
	reactionPickerHeight,
	disableShowingAnimatedImages,
	recentlyUsedEmojis,
} = defaultStore.reactiveState;

const unicodeEmojiSkinTones = [
	"#FFDC5E",
	"#F7DFCF",
	"#F3D3A3",
	"#D6AE89",
	"#B17F56",
	"#7D523C",
];

const unicodeEmojiSkinToneLabels = [
	i18n.ts._skinTones?.yellow ?? "Yellow",
	i18n.ts._skinTones?.light ?? "Light",
	i18n.ts._skinTones?.mediumLight ?? "Medium Light",
	i18n.ts._skinTones?.medium ?? "Medium",
	i18n.ts._skinTones?.mediumDark ?? "Medium Dark",
	i18n.ts._skinTones?.dark ?? "Dark",
];

const size = computed(() =>
	props.asReactionPicker ? reactionPickerSize.value : 1,
);
const width = computed(() =>
	props.asReactionPicker ? reactionPickerWidth.value : 3,
);
const height = computed(() =>
	props.asReactionPicker ? reactionPickerHeight.value : 2,
);
const customEmojiCategories = emojiCategories;
const customEmojis = instance.emojis;
const q = ref<string | null>(null);
const searchResultCustom = ref<Misskey.entities.CustomEmoji[]>([]);
const searchResultUnicode = ref<UnicodeEmojiDef[]>([]);
const tab = ref<"index" | "custom" | "unicode" | "tags">("index");

watch(q, () => {
	if (emojis.value) emojis.value.scrollTop = 0;

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

	const newQ = q.value.replace(/:/g, "").toLowerCase();

	const searchCustom = () => {
		const max = 16;
		const emojis: {
			lowercased: string;
			lowercasedAliases: string[];
			emoji: Misskey.entities.CustomEmoji;
		}[] = customEmojis.map(emoji => ({
			lowercased: emoji.name.toLowerCase(),
			lowercasedAliases: emoji.aliases.map(alias => alias.toLowerCase()),
			emoji,
		}));
		const matches = new Set<Misskey.entities.CustomEmoji>();

		const exactMatch = emojis.find((emoji) => emoji.lowercased === newQ);
		if (exactMatch) matches.add(exactMatch.emoji);

		if (newQ.includes(" ")) {
			// AND検索
			const keywords = newQ.split(" ");

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

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

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

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

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

		return matches;
	};

	const searchUnicode = () => {
		const max = 32;
		const emojis = emojilist;
		const matches = new Set<UnicodeEmojiDef>();

		const exactMatch = emojis.find((emoji) => emoji.slug === newQ);
		if (exactMatch) matches.add(exactMatch);

		if (newQ.includes(" ")) {
			// AND検索
			const keywords = newQ.split(" ");

			// 名前にキーワードが含まれている
			for (const emoji of emojis) {
				if (keywords.every((keyword) => emoji.slug.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.slug.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.slug.startsWith(newQ)) {
					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(newQ))
				) {
					matches.add(emoji);
					if (matches.size >= max) break;
				}
			}
			if (matches.size >= max) return matches;

			for (const emoji of emojis) {
				if (emoji.slug.includes(newQ)) {
					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(newQ))) {
					matches.add(emoji);
					if (matches.size >= max) break;
				}
			}
		}

		return matches;
	};

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

function focus() {
	if (!["smartphone", "tablet"].includes(deviceKind) && !isTouchUsing) {
		search.value?.focus({
			preventScroll: true,
		});
	}
}

function reset() {
	if (emojis.value) emojis.value.scrollTop = 0;
	q.value = "";
}

function getKey(
	emoji: string | Misskey.entities.CustomEmoji | UnicodeEmojiDef,
): string {
	return typeof emoji === "string" ? emoji : emoji.emoji || `:${emoji.name}:`;
}

function chosen(emoji: any, ev?: MouseEvent) {
	const el =
		ev &&
		((ev.currentTarget ?? ev.target) as HTMLElement | null | undefined);
	if (el) {
		const rect = el.getBoundingClientRect();
		const x = rect.left + el.offsetWidth / 2;
		const y = rect.top + el.offsetHeight / 2;
		os.popup(Ripple, { x, y }, {}, "end");
	}

	const key = getKey(emoji);
	emit("chosen", key, ev);

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

function paste(event: ClipboardEvent) {
	const paste = (event.clipboardData || window.clipboardData).getData("text");
	if (done(paste)) {
		event.preventDefault();
	}
}

function done(query?: any): boolean | void {
	if (query == null) query = q.value;
	if (query == null || typeof query !== "string") return;

	const q2 = query.replaceAll(":", "");
	const exactMatchCustom = customEmojis.find((emoji) => emoji.name === q2);
	if (exactMatchCustom) {
		chosen(exactMatchCustom);
		return true;
	}
	const exactMatchUnicode = emojilist.find(
		(emoji) => emoji.emoji === q2 || emoji.slug === q2,
	);
	if (exactMatchUnicode) {
		chosen(exactMatchUnicode);
		return true;
	}
	if (searchResultCustom.value.length > 0) {
		chosen(searchResultCustom.value[0]);
		return true;
	}
	if (searchResultUnicode.value.length > 0) {
		chosen(searchResultUnicode.value[0]);
		return true;
	}
}

onMounted(() => {
	focus();
});

defineExpose({
	focus,
	reset,
});
</script>

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

	display: flex;
	flex-direction: column;

	&.s1 {
		--eachSize: 40px;
	}

	&.s2 {
		--eachSize: 45px;
	}

	&.s3 {
		--eachSize: 50px;
	}

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

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

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

	&.w4 {
		width: calc((var(--eachSize) * 8) + (#{$pad} * 2));
		--columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
	}

	&.w5 {
		width: calc((var(--eachSize) * 9) + (#{$pad} * 2));
		--columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
	}

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

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

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

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

	&.asDrawer {
		width: 100% !important;

		> .emojis {
			::v-deep(section) {
				> header {
					height: 32px;
					line-height: 32px;
					padding: 0 12px;
					font-size: 15px;
				}

				> .body {
					display: grid;
					grid-template-columns: var(--columns);
					font-size: 30px;

					> .item {
						aspect-ratio: 1 / 1;
						width: auto;
						height: auto;
						min-width: 0;
					}
				}
			}
		}
	}

	> .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: 100%;
		overflow-y: auto;
		overflow-x: hidden;

		scrollbar-width: none;

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

		> .group {
			&: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);
				}
			}

			> .body {
				position: relative;
				padding: $pad;

				> .item {
					position: relative;
					padding: 0;
					width: var(--eachSize);
					height: var(--eachSize);
					contain: strict;
					border-radius: 4px;
					font-size: 24px;

					&: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);
					}

					> .emoji {
						height: 1.25em;
						vertical-align: -0.25em;
						pointer-events: none;
					}
				}
			}

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

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