<!--
    Creates a custom qr code.

    Usage:
    <px-qr-code :content="http://thinkpixellab.com" />

-->
<template>
    <div style="aspect-ratio: 1">
        <div
            class="px-qr-code"
            ref="qrCode"
            :style="{ width: size + 'px', height: size + 'px', opacity: isRendered ? 1 : 0 }"
            role="img"
            :aria-label="label || content"
        ></div>
    </div>
</template>

<script>
import QRCodeStyling from 'qr-code-styling';

export default {
    name: 'px-qr-code',
    props: {
        content: { type: String, default: 'Hello' },
        label: { type: String },
        padding: { type: Number, default: 0 },
        imageSettings: { type: Object, default: () => ({}) },
        qrCodeConfig: { type: Object, default: null },
    },
    data() {
        return { size: 0, isRendered: false, percentageMargin: 0 };
    },
    methods: {
        setScale(retryCount = 0) {
            if (!this.percentageMargin) {
                this.isRendered = true;
                return;
            }

            const MAX_RETRIES = 10;

            const svg = this.$refs.qrCode?.querySelector('svg');
            const rectEls = svg?.querySelectorAll(':scope > rect');
            const imageEls = svg?.querySelectorAll(':scope > image');

            const marginPx = (this.percentageMargin / 100) * svg?.clientWidth;
            const scale = (svg?.clientWidth - marginPx * 2) / svg?.clientWidth;

            // the second rect is the qrcode
            const qrRect = rectEls?.[1];
            const qrImage = imageEls?.[0];

            if (!qrRect || !qrImage || !svg) {
                if (retryCount < MAX_RETRIES) {
                    setTimeout(() => {
                        this.setScale(retryCount + 1);
                    }, 25);
                } else {
                    // Optionally, log a warning or handle the error
                    // console.warn('QR rect or image not found after maximum retries.');
                }
                return;
            }

            this.isRendered = true;

            qrRect.style.transform = `scale(${scale})`;
            qrImage.style.transform = `scale(${scale})`;
        },
        setupQRCode() {
            this.size = Math.min(this.$el.clientWidth, this.$el.clientHeight);

            if (this.size <= 0) {
                // setup a size observer to wait for a size, if size is 0 the qr code throws an exception
                this.setupSizeObserver = new ResizeObserver(entries => {
                    const size = Math.min(this.$el.clientWidth, this.$el.clientHeight);
                    if (size > 0) {
                        this.setupSizeObserver.disconnect();
                        this.setupSizeObserver = null;
                        this.setupQRCode();
                    }
                });

                this.setupSizeObserver.observe(this.$el);

                return;
            }

            const { margin, ...rest } = this.qrCodeConfig || {};

            // margin is a percentage of the svg width
            this.percentageMargin = margin ? Math.min(Math.max(0, margin), 100) : 0;

            const options = {
                width: this.size,
                height: this.size,
                image: this.imageSettings?.src || null,
                imageOptions: {
                    hideBackgroundDots: false,
                    imageSize: this.imageSettings?.size || 0.1,
                    margin: 0,
                    crossOrigin: 'anonymous',
                },
                data: this.content,
                ...rest,
                type: 'svg',
            };

            if (!this.percentageMargin) {
                this.isRendered = true;
            }

            this.qrCode = new QRCodeStyling(options);

            this.onSizeChange = () => {
                const containerSize = Math.min(this.$el.clientWidth, this.$el.clientHeight);
                this.size = containerSize;

                if (!this.qrCode._qr) {
                    return;
                }

                // 1. Get module count
                const moduleCount = this.qrCode._qr.getModuleCount();

                const naturalModSize = containerSize / moduleCount;

                // 2. the qr code lib uses integer pixel sizes, so we need to round down
                const moduleSize = Math.floor(naturalModSize);

                // 3. recalculate actual size to position the qr code in the top left corner
                const qrSize = moduleCount * moduleSize;

                // 4. Update QR options
                options.width = qrSize;
                options.height = qrSize;

                this.qrCode.update(options);

                this.$nextTick(() => {
                    this.setScale();
                });
            };

            this.resizeObserver = new ResizeObserver(this.onSizeChange);
            this.resizeObserver.observe(this.$el);

            this.$nextTick(() => {
                this.qrCode.append(this.$refs.qrCode);

                this.onSizeChange();

                if (this.percentageMargin) {
                    this.qrCode.getRawData('svg').then(() => {
                        // QR code has been rendered
                        this.$nextTick(() => {
                            this.setScale();
                        });
                    });
                }
            });
        },
    },
    mounted() {
        this.setupQRCode();
    },
    beforeUnmount() {
        this.resizeObserver?.disconnect();
        this.setupSizeObserver?.disconnect();
    },
};
</script>

<style lang="scss">
.px-qr-code {
    svg {
        width: 100%;
        height: 100%;

        rect,
        image {
            transform-origin: center;
        }
    }
}
</style>
