<template>
	<img
		v-if="customEmoji && !glyphSvg"
		class="mk-emoji custom"
		:class="{ normal, noStyle }"
		:src="url"
		:alt="alt"
		:title="alt"
		decoding="async"
		loading="lazy"
	/>
	<span
		v-else-if="customEmoji && glyphSvg"
		class="mk-emoji custom glyph"
		:class="{ normal, noStyle }"
		:title="alt"
		v-html="glyphSvg"
	/>
	<img
		v-else-if="char && !useOsNativeEmojis"
		class="mk-emoji"
		:src="url"
		:alt="alt"
		:title="alt"
		decoding="async"
		loading="lazy"
	/>
	<span v-else-if="char && useOsNativeEmojis">{{ char }}</span>
	<span v-else>{{ emoji }}</span>
</template>

<script lang="ts" setup>
import { computed, ref, watch } from "vue";
import type { CustomEmoji } from "iceshrimp-sdk/built/entities";
import { getStaticImageUrl } from "@/scripts/get-static-image-url";
import { char2filePath } from "@/scripts/twemoji-base";
import { defaultStore } from "@/store";
import { instance } from "@/instance";
import * as os from "@/os";

type ResolvedCustomEmoji = {
	id?: string;
	name: string;
	url: string;
	category?: string;
	aliases?: string[];
	glyph?: boolean;
	glyphUrl?: string | null;
};

const props = defineProps<{
	emoji: string;
	normal?: boolean;
	noStyle?: boolean;
	customEmojis?: CustomEmoji[];
	isReaction?: boolean;
}>();

const customWrapper = computed(() => {
	if (props.emoji.length < 3) return null;
	const wrapper = props.emoji[0];
	if ((wrapper !== ":" && wrapper !== ";") || props.emoji.at(-1) !== wrapper) {
		return null;
	}

	return wrapper;
});
const isCustom = computed(() => customWrapper.value != null);
const char = computed(() => (isCustom.value ? null : props.emoji));
const useOsNativeEmojis = computed(
	() => defaultStore.state.useOsNativeEmojis && !props.isReaction,
);
const ce = computed(() => props.customEmojis ?? instance.emojis ?? []);
const remoteCustomEmoji = ref<ResolvedCustomEmoji | null>(null);
const glyphSvg = ref<string | null>(null);
const customEmojiName = computed(() =>
	isCustom.value ? props.emoji.slice(1, -1) : null,
);
const customEmoji = computed(() => {
	if (!isCustom.value) return null;
	return (
		ce.value.find((x) => x.name === customEmojiName.value) ??
		remoteCustomEmoji.value
	);
});
const wantsGlyph = computed(
	() => customWrapper.value === ";" && !!(customEmoji.value as ResolvedCustomEmoji | null)?.glyph,
);
const resolvedEmojiCache = new Map<string, ResolvedCustomEmoji | null>();

watch(
	customEmojiName,
	async (name) => {
		remoteCustomEmoji.value = null;
		glyphSvg.value = null;
		if (!name || ce.value.some((x) => x.name === name)) return;

		if (resolvedEmojiCache.has(name)) {
			remoteCustomEmoji.value = resolvedEmojiCache.get(name) ?? null;
			return;
		}

		const emoji = await os.api("emoji", { name }).catch(() => null);
		resolvedEmojiCache.set(name, emoji as ResolvedCustomEmoji | null);
		if (customEmojiName.value === name) {
			remoteCustomEmoji.value = emoji as ResolvedCustomEmoji | null;
		}
	},
	{ immediate: true },
);
watch(
	[customEmoji, wantsGlyph],
	async (emoji) => {
		glyphSvg.value = null;
		const [currentEmoji, currentWantsGlyph] = emoji;
		if (!currentWantsGlyph || !currentEmoji) return;

		const sourceUrl = (currentEmoji as ResolvedCustomEmoji).glyphUrl ?? currentEmoji.url;

		const response = await fetch(sourceUrl, { credentials: "same-origin" }).catch(
			() => null,
		);
		if (!response?.ok) return;

		const text = await response.text();
		const svg = text.match(/<svg[\s\S]*<\/svg>/i)?.[0];
		if (!svg) return;

		const cleaned = svg
			.replace(/<\?xml[\s\S]*?\?>/gi, "")
			.replace(/<!doctype[\s\S]*?>/gi, "")
			.replace(/<script[\s\S]*?<\/script>/gi, "")
			.replace(/<foreignObject[\s\S]*?<\/foreignObject>/gi, "")
			.replace(/\s(on[a-z]+|href|xlink:href)=(".*?"|'.*?'|[^\s>]+)/gi, "")
			.replace(/\sfill=(".*?"|'.*?'|[^\s>]+)/gi, ' fill="currentColor"')
			.replace(/\sstroke=(".*?"|'.*?'|[^\s>]+)/gi, ' stroke="currentColor"');

		if (customEmojiName.value === currentEmoji.name) {
			glyphSvg.value = cleaned;
		}
	},
	{ immediate: true },
);
const url = computed(() => {
	if (char.value) {
		return char2filePath(char.value);
	} else if (!customEmoji.value) {
		return "";
	} else {
		return defaultStore.state.disableShowingAnimatedImages
			? getStaticImageUrl(customEmoji.value.url)
			: customEmoji.value.url;
	}
});
const alt = computed(() =>
	customEmoji.value
		? `${customWrapper.value ?? ":"}${customEmoji.value.name}${customWrapper.value ?? ":"}`
		: char.value,
);
</script>

<style lang="scss" scoped>
.mk-emoji {
	height: 1.25em;
	vertical-align: -0.25em;

	&.custom {
		height: 2em;
		vertical-align: middle;
		transition: transform 0.2s ease;

		&:hover {
			transform: scale(1.2);
		}

		&.glyph {
			display: inline-block;
			width: 1em;
			height: 1em;
			line-height: 1;
			vertical-align: -0.125em;

			svg {
				display: block;
				width: 1em;
				height: 1em;
				fill: currentColor;
				stroke: currentColor;
			}
		}

		&.normal {
			height: 1.25em;
			vertical-align: -0.25em;

			&:hover {
				transform: none;
			}
		}
	}

	&.noStyle {
		height: auto !important;
	}
}
</style>
