<script lang="ts">
    import { apiBaseURL } from "../config"
    import { currentLanguage, DEFAULT_LANGUAGE } from "../lib/i18n"
    import { apiBaseOverride } from "../lib/store"
    import { resolveApiAssetUrl } from "../lib/utils"

    interface Props {
        id?: string
        entry?: MedialibEntry | null
        filter?: string | null
        noPlaceholder?: boolean
        alt?: string
        caption?: string
        showCaption?: boolean
        minWidth?: number
        widthMultiplier?: number
        lazy?: boolean
        class?: string
        style?: string
    }

    let {
        id = "",
        entry = null,
        filter = null,
        noPlaceholder = false,
        alt = "",
        caption = "",
        showCaption = false,
        minWidth = 0,
        widthMultiplier = 1,
        lazy = false,
        class: className = "",
        style = "",
    }: Props = $props()

    let imgEl = $state<HTMLImageElement | null>(null)
    let currentFilter = $state<string>("l-webp")
    const effectiveId = $derived(entry?.id || id || "")
    const fileSrc = $derived(resolveFileSrc(entry?.file?.src, entry?.id || effectiveId))
    const placeholderSrc = $derived(
        resolveApiAssetUrl("/assets/img/placeholder-image.svg") || "/assets/img/placeholder-image.svg"
    )

    $effect(() => {
        if (filter) {
            currentFilter = filter
            return
        }

        if (minWidth) {
            currentFilter = getFilterForWidth(minWidth * (widthMultiplier || 1))
        }
    })

    function getMeasuredWidth(node: HTMLImageElement): number {
        const pictureElement = node.closest("picture") as HTMLElement | null
        const figureElement = node.closest("figure") as HTMLElement | null
        const widthCandidates = [
            node.getBoundingClientRect().width,
            node.width,
            node.parentElement?.getBoundingClientRect().width || 0,
            pictureElement?.getBoundingClientRect().width || 0,
            figureElement?.getBoundingClientRect().width || 0,
        ]
        const measuredWidth = Math.max(...widthCandidates)
        const width = minWidth ? Math.max(measuredWidth, minWidth) : measuredWidth
        return width * (widthMultiplier || 1)
    }

    function getFilterForWidth(imgWidth: number): string {
        const effectiveWidth = imgWidth

        if (effectiveWidth <= 90) return "xs-webp"
        if (effectiveWidth <= 300) return "s-webp"
        if (effectiveWidth <= 600) return "m-webp"
        if (effectiveWidth <= 1200) return "l-webp"
        if (effectiveWidth <= 2000) return "xl-webp"
        return "xxl-webp"
    }

    function clampFocalPoint(value?: number): number | null {
        if (typeof value !== "number" || Number.isNaN(value)) {
            return null
        }

        return Math.min(1, Math.max(0, value))
    }

    function getFocalPoint(entry: MedialibEntry | null): { x?: number; y?: number } | null {
        const fileWithFocalPoint = entry?.file as { focalPoint?: { x?: number; y?: number } } | undefined
        return fileWithFocalPoint?.focalPoint || null
    }

    function getObjectPosition(entry: MedialibEntry | null): string | null {
        const focalPoint = getFocalPoint(entry)
        const x = clampFocalPoint(focalPoint?.x)
        const y = clampFocalPoint(focalPoint?.y)

        if (x === null || y === null) {
            return "50% 50%"
        }

        return `${x * 100}% ${y * 100}%`
    }

    function isRasterImage(entry: MedialibEntry | null): boolean {
        if (entry?.file?.type?.match(/^image\/(png|jpe?g|webp)/)) return true
        // Fallback: check file extension when MIME type is missing (e.g. public projection)
        if (entry?.file?.src?.match(/\.(jpe?g|png|webp)$/i)) return true
        return false
    }

    function resolveFileSrc(src: string | undefined, entryId: string | undefined): string | null {
        if (!src) return null
        if (
            /^(?:https?:)?\/\//.test(src) ||
            src.startsWith("/") ||
            src.startsWith("data:") ||
            src.startsWith("blob:")
        ) {
            return src
        }
        if (!entryId) return null
        const normalizedApiBase = ($apiBaseOverride || apiBaseURL).replace(/\/+$/, "")
        return `${normalizedApiBase}/medialib/${entryId}/${src.replace(/^\/+/, "")}`
    }

    $effect(() => {
        const el = imgEl
        if (!el || filter || !entry || !isRasterImage(entry)) return
        if (typeof ResizeObserver === "undefined") return

        let maxObservedWidth = 0
        let deferredUpdateFrame: number | null = null

        const updateFilter = () => {
            const width = getMeasuredWidth(el)
            if (width <= maxObservedWidth) return
            maxObservedWidth = width
            currentFilter = getFilterForWidth(width)
        }

        const observer = new ResizeObserver(updateFilter)
        const observedElements = [el, el.parentElement, el.closest("picture"), el.closest("figure")].filter(
            (element, index, all) => element && all.indexOf(element) === index
        ) as Element[]

        observedElements.forEach((element) => observer.observe(element))
        updateFilter()
        deferredUpdateFrame = requestAnimationFrame(() => {
            deferredUpdateFrame = null
            updateFilter()
        })

        return () => {
            observer.disconnect()
            if (deferredUpdateFrame !== null) {
                cancelAnimationFrame(deferredUpdateFrame)
            }
        }
    })

    function getSrc(src: string | null, entry: MedialibEntry | null): string {
        if (!src) return placeholderSrc
        if (!isRasterImage(entry)) return src
        if (filter) return src + `?filter=${filter}`
        return src + `?filter=${currentFilter}`
    }

    function resolveLocalizedText(value: string | LocalizedText | undefined, lang: string): string {
        if (!value) return ""
        if (typeof value === "string") return value

        return value[lang] || value[DEFAULT_LANGUAGE] || Object.values(value).find((entry) => !!entry) || ""
    }

    function getAltText(entry: MedialibEntry | null): string {
        return alt || resolveLocalizedText(entry?.alt, $currentLanguage)
    }
</script>

{#if entry && fileSrc}
    {#if showCaption && caption}
        <figure>
            <picture>
                <img
                    bind:this={imgEl}
                    src={getSrc(fileSrc, entry)}
                    alt={getAltText(entry)}
                    data-entry-id={effectiveId}
                    class={className}
                    style:object-position={getObjectPosition(entry)}
                    loading={lazy ? "lazy" : undefined}
                    {style}
                />
            </picture>
            <figcaption class="mt-2 text-sm text-gray-500 text-center italic">
                {@html caption}
            </figcaption>
        </figure>
    {:else}
        <picture>
            <img
                bind:this={imgEl}
                src={getSrc(fileSrc, entry)}
                alt={getAltText(entry)}
                data-entry-id={effectiveId}
                class={className}
                style:object-position={getObjectPosition(entry)}
                loading={lazy ? "lazy" : undefined}
                {style}
            />
        </picture>
    {/if}
{:else if !noPlaceholder && (effectiveId || entry)}
    <picture>
        <img src={placeholderSrc} alt="not found" data-entry-id={effectiveId} class={className} />
    </picture>
{/if}
