<template>
    <div
        :class="[
            'prismic-media',
            { 'fill-space': fillSpace },
            `fit-${fit}`,
            { 'video-loaded': videoSrc && videoLoaded },
        ]"
        :style="{ '--aspect': cmpAspect + '%' }"
    >
        <div class="image-sizer">
            <transition :name="transition">
                <img
                    v-show="loaded"
                    ref="image"
                    :src="imageSrc"
                    :srcset="srcset"
                    :width="width"
                    :height="height"
                    :alt="imageAlt"
                    @load="loaded = true"
                />
            </transition>

            <template v-if="videoSrc.length">
                <video
                    v-show="videoLoaded"
                    :height="height"
                    :width="width"
                    :src="videoSrc"
                    :muted="muted"
                    :autoplay="autoplay"
                    ref="video"
                    playsinline
                    loop
                    @canplay="videoLoaded = true"
                />
            </template>
        </div>
    </div>
</template>

<script setup>
import { ref, onMounted, computed } from 'vue'

const props = defineProps({
    image: {
        type: Object,
        default: () => {},
        required: true,
    },
    video: [Object, String],
    aspect: {
        type: [String, Number],
        default: () => -1,
    },
    transition: {
        type: String,
        default: () => 'fade',
    },
    fillSpace: {
        type: Boolean,
        default: () => false,
    },
    fit: {
        type: String,
        default: () => 'cover',
    },
    muted: {
        type: Boolean,
        default: () => true,
    },
    autoplay: {
        type: Boolean,
        default: () => true,
    },
})

const image = ref()
const video = ref()
const loaded = ref(false)
const videoLoaded = ref(false)

// Set loaded to true if image is in cache
onMounted(async () => {
    await new Promise((res) => setTimeout(res, 50))
    if (image.value?.complete) loaded.value = true
    if (video.value?.readyState >= 3) videoLoaded.value = true
})

const imageSrc = computed(() => props.image?.url || '')
const imageAlt = computed(() => props.image?.alt || '')
const videoSrc = computed(() => props.video?.url || props.video || '')
const width = computed(() => props.image?.dimensions?.width || 0)
const height = computed(() => props.image?.dimensions?.height || 0)

const cmpAspect = computed(() => {
    // calculate if no aspect provided
    if (props.aspect === -1) {
        return (height.value / width.value) * 100
    }

    // otherwise, parse provided aspect, handling both 56.25 and 0.5625 style
    const toParse = parseFloat(props.aspect)
    return toParse <= 1 ? toParse * 100 : toParse
})
const srcset = computed(() => {
    return [400, 800, 1024, 1280, 1536, 2048, 2560]
        .map((size) => {
            const w = size === null ? width.value : size
            const h = Math.round(width.value / (cmpAspect.value / 100))
            return imageSrc.value + `&w=${w} ${w}w`
        })
        .join(', ')
})
</script>

<style lang="scss">
.prismic-media {
    position: relative;
    width: 100%;

    .image-sizer {
        overflow: hidden;
        padding-bottom: var(--aspect);

        & > * {
            position: absolute;
            height: 100%;
            width: 100%;
            left: 0;
            top: 0;
        }
    }

    // fill space
    &.fill-space {
        position: absolute;
        bottom: 0;
        right: 0;
        left: 0;
        top: 0;

        .image-sizer {
            padding-bottom: 0;
            position: absolute;
            top: 0;
            right: 0;
            bottom: 0;
            left: 0;
        }
    }

    // fits
    &.fit-cover .image-sizer > * {
        object-fit: cover;
    }
    &.fit-contain .image-sizer > * {
        object-fit: contain;
    }

    /* Hide background image on video load */
    &.video-loaded img {
        opacity: 0;
    }
}
</style>
