<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->

<template>
<MkModalWindow
	ref="dialogEl"
	:width="1000"
	:height="600"
	:scroll="false"
	:withOkButton="false"
	@close="cancel()"
	@closed="emit('closed')"
>
	<template #header><i class="ti ti-code"></i> {{ i18n.ts._embedCodeGen.title }}</template>

	<div :class="$style.embedCodeGenRoot">
		<Transition
			mode="out-in"
			:enterActiveClass="$style.transition_x_enterActive"
			:leaveActiveClass="$style.transition_x_leaveActive"
			:enterFromClass="$style.transition_x_enterFrom"
			:leaveToClass="$style.transition_x_leaveTo"
		>
			<div v-if="phase === 'input'" key="input" :class="$style.embedCodeGenInputRoot">
				<div :class="[$style.embedCodeGenPreviewRoot, prefer.s.animation ? $style.animatedBg : null]">
					<MkLoading v-if="iframeLoading" :class="$style.embedCodeGenPreviewSpinner"/>
					<div :class="$style.embedCodeGenPreviewWrapper">
						<div class="_acrylic" :class="$style.embedCodeGenPreviewTitle">{{ i18n.ts.preview }}</div>
						<div ref="resizerRootEl" :class="$style.embedCodeGenPreviewResizerRoot" inert>
							<div
								:class="$style.embedCodeGenPreviewResizer"
								:style="{ transform: iframeStyle }"
							>
								<iframe
									ref="iframeEl"
									:src="embedPreviewUrl"
									:class="$style.embedCodeGenPreviewIframe"
									:style="{ height: `${iframeHeight}px` }"
									@load="iframeOnLoad"
								></iframe>
							</div>
						</div>
					</div>
				</div>
				<div :class="$style.embedCodeGenSettings" class="_gaps">
					<MkInput v-if="isEmbedWithScrollbar" v-model="maxHeight" type="number" :min="0">
						<template #label>{{ i18n.ts._embedCodeGen.maxHeight }}</template>
						<template #suffix>px</template>
						<template #caption>{{ i18n.ts._embedCodeGen.maxHeightDescription }}</template>
					</MkInput>
					<MkSelect v-model="colorMode" :items="colorModeDef">
						<template #label>{{ i18n.ts.theme }}</template>
					</MkSelect>
					<MkSwitch v-if="isEmbedWithScrollbar" v-model="header">{{ i18n.ts._embedCodeGen.header }}</MkSwitch>
					<MkSwitch v-model="rounded">{{ i18n.ts._embedCodeGen.rounded }}</MkSwitch>
					<MkSwitch v-model="border">{{ i18n.ts._embedCodeGen.border }}</MkSwitch>
					<MkInfo v-if="isEmbedWithScrollbar && (!maxHeight || maxHeight <= 0)" warn>{{ i18n.ts._embedCodeGen.maxHeightWarn }}</MkInfo>
					<MkInfo v-if="typeof maxHeight === 'number' && (maxHeight <= 0 || maxHeight > 700)">{{ i18n.ts._embedCodeGen.previewIsNotActual }}</MkInfo>
					<div class="_buttons">
						<MkButton :disabled="iframeLoading" @click="applyToPreview">{{ i18n.ts._embedCodeGen.applyToPreview }}</MkButton>
						<MkButton :disabled="iframeLoading" primary @click="generate">{{ i18n.ts._embedCodeGen.generateCode }} <i class="ti ti-arrow-right"></i></MkButton>
					</div>
				</div>
			</div>
			<div v-else-if="phase === 'result'" key="result" :class="$style.embedCodeGenResultRoot">
				<div :class="$style.embedCodeGenResultWrapper" class="_gaps">
					<div class="_gaps_s">
						<div :class="$style.embedCodeGenResultHeadingIcon"><i class="ti ti-check"></i></div>
						<div :class="$style.embedCodeGenResultHeading">{{ i18n.ts._embedCodeGen.codeGenerated }}</div>
						<div :class="$style.embedCodeGenResultDescription">{{ i18n.ts._embedCodeGen.codeGeneratedDescription }}</div>
					</div>
					<div class="_gaps_s">
						<MkCode :code="result" lang="html" :forceShow="true" :copyButton="false" :class="$style.embedCodeGenResultCode"/>
						<MkButton :class="$style.embedCodeGenResultButtons" rounded primary @click="doCopy"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton>
					</div>
					<MkButton :class="$style.embedCodeGenResultButtons" rounded transparent @click="close">{{ i18n.ts.close }}</MkButton>
				</div>
			</div>
		</Transition>
	</div>
</MkModalWindow>
</template>

<script setup lang="ts">
import { useTemplateRef, ref, computed, nextTick, onMounted, onDeactivated, onUnmounted } from 'vue';
import { url } from '@@/js/config.js';
import { embedRouteWithScrollbar } from '@@/js/embed-page.js';
import type { EmbeddableEntity, EmbedParams } from '@@/js/embed-page.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkButton from '@/components/MkButton.vue';
import MkCode from '@/components/MkCode.vue';
import MkInfo from '@/components/MkInfo.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { useMkSelect } from '@/composables/use-mkselect.js';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { normalizeEmbedParams, getEmbedCode } from '@/utility/get-embed-code.js';
import { prefer } from '@/preferences.js';

const emit = defineEmits<{
	(ev: 'ok'): void;
	(ev: 'cancel'): void;
	(ev: 'closed'): void;
}>();

const props = defineProps<{
	entity: EmbeddableEntity;
	id: string;
	params?: EmbedParams;
}>();

//#region Modalの制御
const dialogEl = useTemplateRef('dialogEl');

function cancel() {
	emit('cancel');
	dialogEl.value?.close();
}

function close() {
	dialogEl.value?.close();
}

const phase = ref<'input' | 'result'>('input');
//#endregion

//#region 埋め込みURL生成・カスタマイズ

// 本URL生成用params
const paramsForUrl = computed<EmbedParams>(() => ({
	header: header.value,
	maxHeight: typeof maxHeight.value === 'number' ? Math.max(0, maxHeight.value) : undefined,
	colorMode: colorMode.value === 'auto' ? undefined : colorMode.value,
	rounded: rounded.value,
	border: border.value,
}));

// プレビュー用params（手動で更新を掛けるのでref）
const paramsForPreview = ref<EmbedParams>(props.params ?? {});

const embedPreviewUrl = computed(() => {
	const paramClass = new URLSearchParams(normalizeEmbedParams(paramsForPreview.value));
	if (paramClass.has('maxHeight')) {
		const maxHeight = parseInt(paramClass.get('maxHeight')!);
		paramClass.set('maxHeight', maxHeight === 0 ? '500' : Math.min(maxHeight, 700).toString()); // プレビューであまりにも縮小されると見づらいため、700pxまでに制限
	}
	return `${url}/embed/${props.entity}/${props.id}${paramClass.toString() ? '?' + paramClass.toString() : ''}`;
});

const isEmbedWithScrollbar = computed(() => embedRouteWithScrollbar.includes(props.entity));
const header = ref(props.params?.header ?? true);
const maxHeight = ref(props.params?.maxHeight !== 0 ? props.params?.maxHeight ?? null : 500);

const {
	model: colorMode,
	def: colorModeDef,
} = useMkSelect({
	items: [
		{ value: 'auto', label: i18n.ts.syncDeviceDarkMode },
		{ value: 'light', label: i18n.ts.light },
		{ value: 'dark', label: i18n.ts.dark },
	],
	initialValue: props.params?.colorMode ?? 'auto',
});

const rounded = ref(props.params?.rounded ?? true);
const border = ref(props.params?.border ?? true);

function applyToPreview() {
	const currentPreviewUrl = embedPreviewUrl.value;

	paramsForPreview.value = {
		header: header.value,
		maxHeight: typeof maxHeight.value === 'number' ? Math.max(0, maxHeight.value) : undefined,
		colorMode: colorMode.value === 'auto' ? undefined : colorMode.value,
		rounded: rounded.value,
		border: border.value,
	};

	nextTick(() => {
		if (currentPreviewUrl === embedPreviewUrl.value) {
			// URLが変わらなくてもリロード
			iframeEl.value?.contentWindow?.window.location.reload();
		}
	});
}

const result = ref('');

function generate() {
	result.value = getEmbedCode(`/embed/${props.entity}/${props.id}`, paramsForUrl.value);
	phase.value = 'result';
}

function doCopy() {
	copyToClipboard(result.value);
}
//#endregion

//#region プレビューのリサイズ
const resizerRootEl = useTemplateRef('resizerRootEl');
const iframeLoading = ref(true);
const iframeEl = useTemplateRef('iframeEl');
const iframeHeight = ref(0);
const iframeScale = ref(1);
const iframeStyle = computed(() => {
	return `translate(-50%, -50%) scale(${iframeScale.value})`;
});
const resizeObserver = new ResizeObserver(() => {
	calcScale();
});

function iframeOnLoad() {
	iframeEl.value?.contentWindow?.addEventListener('beforeunload', () => {
		iframeLoading.value = true;
		nextTick(() => {
			iframeHeight.value = 0;
			iframeScale.value = 1;
		});
	});
}

function windowEventHandler(event: MessageEvent) {
	if (event.source !== iframeEl.value?.contentWindow) {
		return;
	}
	if (event.data.type === 'misskey:embed:ready') {
		iframeEl.value!.contentWindow?.postMessage({
			type: 'misskey:embedParent:registerIframeId',
			payload: {
				iframeId: 'embedCodeGen', // 同じタイミングで複数のembed iframeがある際の区別用なのでここではなんでもいい
			},
		});
	}
	if (event.data.type === 'misskey:embed:changeHeight') {
		iframeHeight.value = event.data.payload.height;
		nextTick(() => {
			calcScale();
			iframeLoading.value = false; // 初回の高さ変更まで待つ
		});
	}
}

function calcScale() {
	if (!resizerRootEl.value) return;
	const previewWidth = resizerRootEl.value.clientWidth - 40; // 左右の余白 20pxずつ
	const previewHeight = resizerRootEl.value.clientHeight - 40; // 上下の余白 20pxずつ
	const iframeWidth = 500;
	const scale = Math.min(previewWidth / iframeWidth, previewHeight / iframeHeight.value, 1); // 拡大はしないので1を上限に
	iframeScale.value = scale;
}

onMounted(() => {
	window.addEventListener('message', windowEventHandler);
	if (!resizerRootEl.value) return;
	resizeObserver.observe(resizerRootEl.value);
});

function reset() {
	window.removeEventListener('message', windowEventHandler);
	resizeObserver.disconnect();

	// プレビューのリセット
	iframeHeight.value = 0;
	iframeScale.value = 1;
	iframeLoading.value = true;
	result.value = '';
	phase.value = 'input';
}

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

onUnmounted(() => {
	reset();
});
//#endregion
</script>

<style module>
.transition_x_enterActive,
.transition_x_leaveActive {
	transition: opacity 0.3s cubic-bezier(0,0,.35,1), transform 0.3s cubic-bezier(0,0,.35,1);
}
.transition_x_enterFrom {
	opacity: 0;
	transform: translateX(50px);
}
.transition_x_leaveTo {
	opacity: 0;
	transform: translateX(-50px);
}

.embedCodeGenRoot {
	container-type: inline-size;
	height: 100%;
}

.embedCodeGenInputRoot {
	height: 100%;
	display: grid;
	grid-template-columns: 1fr 400px;
}

.embedCodeGenPreviewRoot {
	position: relative;
	cursor: not-allowed;
	background-color: var(--MI_THEME-bg);
	background-image: linear-gradient(135deg, transparent 30%, var(--MI_THEME-panel) 30%, var(--MI_THEME-panel) 50%, transparent 50%, transparent 80%, var(--MI_THEME-panel) 80%, var(--MI_THEME-panel) 100%);
	background-size: 20px 20px;
}

.animatedBg {
	animation: bg 1.2s linear infinite;
}

@keyframes bg {
	0% { background-position: 0 0; }
	100% { background-position: -20px -20px; }
}

.embedCodeGenPreviewWrapper {
	display: flex;
	flex-direction: column;
	height: 100%;
	pointer-events: none;
	user-select: none;
	-webkit-user-drag: none;
}

.embedCodeGenPreviewTitle {
	position: absolute;
	z-index: 100;
	top: 8px;
	left: 8px;
	padding: 6px 10px;
	border-radius: 6px;
	font-size: 85%;
}

.embedCodeGenPreviewSpinner {
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	pointer-events: none;
	user-select: none;
	-webkit-user-drag: none;
}

.embedCodeGenPreviewResizerRoot {
	position: relative;
	flex: 1 0;
}

.embedCodeGenPreviewResizer {
	position: absolute;
	top: 50%;
	left: 50%;
}

.embedCodeGenPreviewIframe {
	display: block;
	border: none;
	width: 500px;
	color-scheme: light dark;
}

.embedCodeGenSettings {
	padding: 24px;
	overflow-y: scroll;
}

.embedCodeGenResultRoot {
	box-sizing: border-box;
	padding: 24px;
	height: 100%;
	max-width: 700px;
	margin: 0 auto;
	display: flex;
	align-items: center;
}

.embedCodeGenResultHeading {
	text-align: center;
	font-size: 1.2em;
}

.embedCodeGenResultHeadingIcon {
	margin: 0 auto;
	background-color: var(--MI_THEME-accentedBg);
	color: var(--MI_THEME-accent);
	text-align: center;
	height: 64px;
	width: 64px;
	font-size: 24px;
	line-height: 64px;
	border-radius: 50%;
}

.embedCodeGenResultDescription {
	text-align: center;
	white-space: pre-wrap;
}

.embedCodeGenResultWrapper,
.embedCodeGenResultCode {
	width: 100%;
}

.embedCodeGenResultButtons {
	margin: 0 auto;
}

@container (max-width: 800px) {
	.embedCodeGenInputRoot {
		grid-template-columns: 1fr;
		grid-template-rows: 1fr 1fr;
	}
}
</style>
