<template>
	<div>
		<span v-if="!available">{{ i18n.ts.waiting }}<MkEllipsis /></span>
		<div ref="captchaEl"></div>
	</div>
</template>

<script lang="ts" setup>
import { ref, computed, onMounted, onBeforeUnmount, watch } from "vue";
import { defaultStore } from "@/store";
import { i18n } from "@/i18n";

type Captcha = {
	render(
		container: string | Node,
		options: {
			readonly [_ in
				| "sitekey"
				| "theme"
				| "type"
				| "size"
				| "tabindex"
				| "callback"
				| "expired"
				| "expired-callback"
				| "error-callback"
				| "endpoint"]?: unknown;
		}
	): string;
	remove(id: string): void;
	execute(id: string): void;
	reset(id?: string): void;
	getResponse(id: string): string;
};

type CaptchaProvider = "hcaptcha" | "recaptcha";

type CaptchaContainer = {
	readonly [_ in CaptchaProvider]?: Captcha;
};

declare global {
	interface Window extends CaptchaContainer {}
}

const props = defineProps<{
	provider: CaptchaProvider;
	sitekey: string;
	modelValue?: string | null;
}>();

const emit = defineEmits<{
	(ev: "update:modelValue", v: string | null): void;
}>();

const available = ref(false);

const captchaEl = ref<HTMLDivElement | undefined>();

const variable = computed(() => {
	switch (props.provider) {
		case "hcaptcha":
			return "hcaptcha";
		case "recaptcha":
			return "grecaptcha";
	}
});

const loaded = !!window[variable.value];

const src = computed(() => {
	switch (props.provider) {
		case "hcaptcha":
			return "https://js.hcaptcha.com/1/api.js?render=explicit&recaptchacompat=off";
		case "recaptcha":
			return "https://www.recaptcha.net/recaptcha/api.js?render=explicit";
	}
});

const captcha = computed<Captcha>(
	() => window[variable.value] || ({} as unknown as Captcha)
);

if (loaded) {
	available.value = true;
} else {
	(
		document.getElementById(props.provider) ||
		document.head.appendChild(
			Object.assign(document.createElement("script"), {
				async: true,
				id: props.provider,
				src: src.value,
			})
		)
	).addEventListener("load", () => (available.value = true));
}

function reset() {
	if (captcha.value.reset) captcha.value.reset();
}

function requestRender() {
	if (captcha.value.render && captchaEl.value instanceof Element) {
		captcha.value.render(captchaEl.value, {
			sitekey: props.sitekey,
			theme: defaultStore.state.darkMode ? "dark" : "light",
			callback: callback,
			"expired-callback": callback,
			"error-callback": callback,
		});
	} else {
		window.setTimeout(requestRender, 1);
	}
}

function callback(response?: string) {
	emit("update:modelValue", typeof response === "string" ? response : null);
}

onMounted(() => {
	if (available.value) {
		requestRender();
	} else {
		watch(available, requestRender);
	}
});

onBeforeUnmount(() => {
	reset();
});

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