<script setup lang="ts">
import type { IconType } from '@bagelink/vue'
import { useDevice } from '@bagelink/vue'
import { computed, ref, onMounted, watchEffect } from 'vue'
import { FONT_AWESOME_ICONS, MATERIAL_ICONS, FONT_AWESOME_BRANDS_ICONS } from './constants'

type AnimateType = 'spin' | 'pulse' | 'bounce' | 'fade-in' | 'slide-up' | 'rotate-in'
type TransitionType = 'slide-up' | 'rotate' | 'scale' | 'fade'

const props = withDefaults(defineProps<{
	icon?: IconType
	name?: IconType
	size?: number | string
	mobileSize?: number | string
	color?: string
	round?: boolean
	weight?: number | string
	fontAwesome?: boolean
	fill?: boolean
	/** Entrance / looping effect. Pure CSS, opt-in. e.g. `animate="spin"`. */
	animate?: AnimateType
	/** Seconds for one cycle of `animate` (spin/pulse/etc.). */
	animateSpeed?: number
	/** Play the `animate` effect once instead of looping. */
	animateOnce?: boolean
	/** Animate the glyph whenever the icon changes (e.g. a play/pause toggle).
	    Defaults ON: pass `transition="rotate"` to pick a style, or
	    `:transition="false"` to disable. */
	transition?: boolean | TransitionType
}>(), {
	size: 1,
	transition: true,
})

const iconRender = computed(() => (props.icon ?? props.name) as IconType)

const { isMobile } = useDevice()

const computedSize = computed(() => {
	if (isMobile.value && props.mobileSize !== undefined) {
		return props.mobileSize
	}
	return props.size
})

const iconRenderType = computed(() => {
	if (props.fontAwesome && (FONT_AWESOME_ICONS.includes(iconRender.value) || FONT_AWESOME_BRANDS_ICONS.includes(iconRender.value))) { return 'font-awesome' }
	if (MATERIAL_ICONS.includes(iconRender.value)) { return 'material' }
	if (FONT_AWESOME_ICONS.includes(iconRender.value) || FONT_AWESOME_BRANDS_ICONS.includes(iconRender.value)) { return 'font-awesome' }
	return 'material'
})

const isFaBrand = computed(() => FONT_AWESOME_BRANDS_ICONS.includes(iconRender.value))

// Hide until required font is ready to avoid ligature text/fallback flash
const isMaterialReady = ref(true)
const isFAFreeReady = ref(true)
const isFABrandsReady = ref(true)

function getFontFaceSet(): FontFaceSet | null {
	if (typeof document === 'undefined') { return null }
	const maybeFonts = (document as unknown as { fonts?: FontFaceSet }).fonts
	return maybeFonts ?? null
}

function supportsFontLoading(): boolean {
	const fonts = getFontFaceSet()
	return fonts !== null && typeof fonts.load === 'function'
}

async function ensureFontLoaded(family: string): Promise<boolean> {
	if (!supportsFontLoading()) { return true }
	try {
		const fonts = getFontFaceSet()
		if (!fonts) { return true }
		if (fonts.check(`1em "${family}"`)) { return true }
		await fonts.load(`1em "${family}"`)
		return fonts.check(`1em "${family}"`)
	} catch {
		return false
	}
}

async function loadMaterialIfNeeded() {
	const ok = await ensureFontLoaded('Material Symbols Outlined')
	isMaterialReady.value = ok
}

async function loadFAFreeIfNeeded() {
	const ok = await ensureFontLoaded('Font Awesome 6 Free')
	isFAFreeReady.value = ok
}

async function loadFABrandsIfNeeded() {
	const ok = await ensureFontLoaded('Font Awesome 6 Brands')
	isFABrandsReady.value = ok
}

onMounted(() => {
	if (!supportsFontLoading()) { return }
	if (iconRenderType.value === 'material') {
		const fonts = getFontFaceSet()
		isMaterialReady.value = fonts ? fonts.check('1em "Material Symbols Outlined"') : true
		void loadMaterialIfNeeded()
	} else if (isFaBrand.value) {
		const fonts = getFontFaceSet()
		isFABrandsReady.value = fonts ? fonts.check('1em "Font Awesome 6 Brands"') : true
		void loadFABrandsIfNeeded()
	} else {
		const fonts = getFontFaceSet()
		isFAFreeReady.value = fonts ? fonts.check('1em "Font Awesome 6 Free"') : true
		void loadFAFreeIfNeeded()
	}
})

watchEffect(() => {
	if (!supportsFontLoading()) { return }
	if (iconRenderType.value === 'material') {
		void loadMaterialIfNeeded()
	} else if (isFaBrand.value) {
		void loadFABrandsIfNeeded()
	} else {
		void loadFAFreeIfNeeded()
	}
})

const isCurrentFontReady = computed(() => {
	if (iconRenderType.value === 'material') { return isMaterialReady.value }
	return isFaBrand.value ? isFABrandsReady.value : isFAFreeReady.value
})

// Entrance / loop animation class + timing.
const animateClass = computed(() => (props.animate ? `bgl_icon--${props.animate}` : null))
const animateStyle = computed(() => {
	if (!props.animate) { return undefined }
	const style: Record<string, string> = {}
	if (props.animateSpeed !== undefined) { style.animationDuration = `${props.animateSpeed}s` }
	if (props.animateOnce) { style.animationIterationCount = '1' }
	return style
})

// Swap transition: animate whenever the glyph changes. `transition` true uses the
// default style; a string picks one. The keyed inner span drives Vue's <Transition>.
const transitionName = computed(() => {
	// `bgl-icon-x-none` has no CSS defined, so the swap is instant when disabled.
	if (props.transition === false) { return 'bgl-icon-x-none' }
	const style = props.transition === true ? 'slide-up' : props.transition
	return `bgl-icon-x-${style}`
})
</script>

<template>
	<Transition :name="transitionName" mode="out-in">
		<span
			v-if="iconRenderType === 'material'" :key="iconRender" class="bgl_icon-font notranslate"
			:class="[animateClass, { 'round flex aspect-ratio-1 justify-content-center': round }]" :style="{
				'fontSize': `${computedSize}rem`,
				color,
				'font-variation-settings': `'FILL' ${fill ? 1 : 0}, 'wght' ${weight || 400}`,
				'width': round ? `calc(${computedSize}rem * 2)` : 'auto',
				'height': round ? `calc(${computedSize}rem * 2)` : 'auto',
				'visibility': isCurrentFontReady ? 'visible' : 'hidden',
				...animateStyle,
			}" translate="no"
		>
			{{ iconRender }}
		</span>
		<span
			v-else-if="iconRenderType === 'font-awesome'" :key="iconRender" class="fa bgl_icon-font notranslate" :class="[
				`fa-${iconRender}`,
				animateClass,
				{
					'fa-brands': isFaBrand,
					'fa-solid': fill,
					'far': !fill && !isFaBrand,
				},
			]" :style="{ 'fontSize': `${computedSize}rem`, color, 'font-variation-settings': `'wght' ${weight || 400}`, 'visibility': isCurrentFontReady ? 'visible' : 'hidden', ...animateStyle }"
			translate="no"
		/>
	</Transition>
</template>

<style>
@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200');
@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css');

[dir='rtl'] .data-row .bgl_icon-font,
[dir='rtl'] .embedded-field .bgl_icon-font,
[dir='rtl'] .rich-text-editor .toolbar .bgl_icon-font {
	transform: rotateY(180deg) !important;
}
</style>

<style>
.bgl_icon-font {
	font-family: 'Material Symbols Outlined', serif !important;
}

.bgl_icon-font.fa {
	font-family: 'Font Awesome 6 Free', serif !important;
}

.bgl_icon-font.fa-brands {
	font-family: 'Font Awesome 6 Brands', serif !important;
}

/* ── Entrance / loop animations (opt-in via `animate`) ─────────────────────── */
.bgl_icon--spin,
.bgl_icon--pulse,
.bgl_icon--bounce { display: inline-block; animation-duration: 1s; animation-iteration-count: infinite; animation-timing-function: linear; }
.bgl_icon--fade-in,
.bgl_icon--slide-up,
.bgl_icon--rotate-in { display: inline-block; animation-duration: 0.3s; animation-iteration-count: 1; animation-timing-function: ease; }

.bgl_icon--spin { animation-name: bgl-icon-spin; }
.bgl_icon--pulse { animation-name: bgl-icon-pulse; animation-timing-function: ease-in-out; }
.bgl_icon--bounce { animation-name: bgl-icon-bounce; animation-timing-function: ease; }
.bgl_icon--fade-in { animation-name: bgl-icon-fade-in; }
.bgl_icon--slide-up { animation-name: bgl-icon-slide-up; }
.bgl_icon--rotate-in { animation-name: bgl-icon-rotate-in; }

@keyframes bgl-icon-spin { to { transform: rotate(360deg); } }
@keyframes bgl-icon-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
@keyframes bgl-icon-bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-20%); } }
@keyframes bgl-icon-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes bgl-icon-slide-up { from { opacity: 0; transform: translateY(0.35em); } to { opacity: 1; transform: translateY(0); } }
@keyframes bgl-icon-rotate-in { from { opacity: 0; transform: rotate(-90deg) scale(0.6); } to { opacity: 1; transform: rotate(0) scale(1); } }

@media (prefers-reduced-motion: reduce) {
	.bgl_icon--spin, .bgl_icon--pulse, .bgl_icon--bounce,
	.bgl_icon--fade-in, .bgl_icon--slide-up, .bgl_icon--rotate-in { animation: none; }
}

/* ── Swap transitions (on `name`/`icon` change; `transition` defaults on) ──── */
.bgl-icon-x-slide-up-enter-active,
.bgl-icon-x-slide-up-leave-active,
.bgl-icon-x-rotate-enter-active,
.bgl-icon-x-rotate-leave-active,
.bgl-icon-x-scale-enter-active,
.bgl-icon-x-scale-leave-active,
.bgl-icon-x-fade-enter-active,
.bgl-icon-x-fade-leave-active { transition: opacity 0.16s ease, transform 0.16s ease; }

.bgl-icon-x-slide-up-enter-from { opacity: 0; transform: translateY(0.3em); }
.bgl-icon-x-slide-up-leave-to { opacity: 0; transform: translateY(-0.3em); }

.bgl-icon-x-rotate-enter-from { opacity: 0; transform: rotate(-90deg); }
.bgl-icon-x-rotate-leave-to { opacity: 0; transform: rotate(90deg); }

.bgl-icon-x-scale-enter-from { opacity: 0; transform: scale(0.5); }
.bgl-icon-x-scale-leave-to { opacity: 0; transform: scale(0.5); }

.bgl-icon-x-fade-enter-from,
.bgl-icon-x-fade-leave-to { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
	.bgl-icon-x-slide-up-enter-active, .bgl-icon-x-slide-up-leave-active,
	.bgl-icon-x-rotate-enter-active, .bgl-icon-x-rotate-leave-active,
	.bgl-icon-x-scale-enter-active, .bgl-icon-x-scale-leave-active,
	.bgl-icon-x-fade-enter-active, .bgl-icon-x-fade-leave-active { transition: none; }
}
</style>

<!-- <style src="./font-awesome.css" /> -->
