(function () {
    'use strict';

    if (!window.Vue || !document.getElementById('qrCodeApp')) return;

    var frameAssets = {
        envelope:18, video:0, tray:22, sketch:13, beer:30, scooter:21, coffee:29, chef:28,
        pointing:20, plain_text:5, separate_footer:2, speech_bottom:11, solid_footer:3,
        outline_text:4, smartphone:1, floating_label:6, clipboard:9, triangle_footer:24,
        spiral:7, arrow_script:25, receipt:17, bow:10, script:16, speech_top:14, ticket:12,
        drink:8, ribbon:26, takeaway:19, cafe_bag:23, round_display:27, shopping_bag:15
    };

    window.Vue.createApp({
        data: function () {
            return {
                form: {
                    content: '', size: '500', level: 'M', foreground: '#111827', background: '#ffffff',
                    moduleStyle: 'square', eyeStyle: 'square', frameStyle: 'none', frameText: 'Scan me!',
                    frameColor: '#111827', frameTextColor: '#ffffff', logo: null
                },
                previewUrl: null,
                previewTimer: null,
                previewController: null,
                requestId: 0,
                frameSvgCache: {}
            };
        },
        mounted: function () {
            var vm = this;
            this.el = {
                content: document.getElementById('qrContent'), size: document.getElementById('qrSize'), level: document.getElementById('qrLevel'),
                foreground: document.getElementById('qrForeground'), background: document.getElementById('qrBackground'), logo: document.getElementById('qrLogo'),
                frameText: document.getElementById('qrFrameText'), frameColor: document.getElementById('qrFrameColor'), frameTextColor: document.getElementById('qrFrameTextColor'),
                generate: document.getElementById('qrGenerate'), download: document.getElementById('qrDownload'), image: document.getElementById('qrImage'),
                empty: document.getElementById('qrEmptyState'), message: document.getElementById('qrMessage'), preview: document.getElementById('qrPreview')
            };

            this.el.generate.innerHTML = '<i class="fa fa-magic"></i> Perbarui QR Code';
            this.el.content.addEventListener('input', function () { vm.form.content = this.value; document.getElementById('qrCharacterCount').textContent = this.value.length; vm.schedule(450); });
            this.el.size.addEventListener('change', function () { vm.form.size = this.value; vm.schedule(100); });
            this.el.level.addEventListener('change', function () { vm.form.level = this.value; vm.markActive('qrLevelOptions', this.value); vm.schedule(120); });
            this.bindColor(this.el.foreground, 'foreground', 'qrForegroundText');
            this.bindColor(this.el.background, 'background', 'qrBackgroundText');
            this.bindColor(this.el.frameColor, 'frameColor', 'qrFrameColorText');
            this.bindColor(this.el.frameTextColor, 'frameTextColor', 'qrFrameTextColorText');
            this.el.frameText.addEventListener('input', function () { vm.form.frameText = this.value; vm.schedule(300); });

            document.querySelectorAll('.qr-design-tabs button').forEach(function (button) {
                button.addEventListener('click', function () {
                    document.querySelectorAll('.qr-design-tabs button').forEach(function (item) { item.classList.toggle('is-active', item === button); });
                    document.querySelectorAll('.qr-design-panel').forEach(function (panel) { panel.classList.toggle('is-active', panel.getAttribute('data-panel') === button.getAttribute('data-tab')); });
                });
            });
            this.bindOptions('qrModuleStyles', 'moduleStyle');
            this.bindOptions('qrEyeStyles', 'eyeStyle');
            this.bindOptions('qrLevelOptions', 'level');
            this.bindOptions('qrFrameStyles', 'frameStyle');
            this.applyFrameThumbnails();

            this.el.logo.addEventListener('change', function () { vm.updateLogo(); });
            ['dragenter','dragover'].forEach(function (name) { document.getElementById('qrLogoDrop').addEventListener(name, function (e) { e.preventDefault(); this.classList.add('is-dragging'); }); });
            ['dragleave','drop'].forEach(function (name) { document.getElementById('qrLogoDrop').addEventListener(name, function (e) { e.preventDefault(); this.classList.remove('is-dragging'); }); });
            document.getElementById('qrLogoDrop').addEventListener('drop', function (e) { if (e.dataTransfer.files.length) { vm.form.logo = e.dataTransfer.files[0]; vm.showLogo(vm.form.logo); vm.schedule(100); } });
            document.getElementById('qrRemoveLogo').addEventListener('click', function () { vm.removeLogo(); vm.schedule(100); });
            this.el.generate.addEventListener('click', function () { if (vm.previewTimer) clearTimeout(vm.previewTimer); vm.request(false, false); });
            this.el.download.addEventListener('click', function () { vm.request(true, false); });
            document.getElementById('qrReset').addEventListener('click', function () { vm.reset(); });
        },
        methods: {
            bindColor: function (picker, key, textId) {
                var vm = this;
                picker.addEventListener('input', function () { vm.form[key] = this.value; document.getElementById(textId).textContent = this.value.toUpperCase(); vm.schedule(180); });
            },
            bindOptions: function (id, key) {
                var vm = this;
                document.querySelectorAll('#' + id + ' button').forEach(function (button) {
                    button.addEventListener('click', function () {
                        var value = button.getAttribute('data-value');
                        vm.markActive(id, value); vm.form[key] = value;
                        if (key === 'level') vm.el.level.value = value;
                        if (key === 'frameStyle') vm.setFrame(value);
                        vm.schedule(120);
                    });
                });
            },
            markActive: function (id, value) {
                document.querySelectorAll('#' + id + ' button').forEach(function (button) { button.classList.toggle('is-active', button.getAttribute('data-value') === value); });
            },
            frameUrl: function (style) {
                return window.qrCodeConfig.frameBase + 'frame-' + frameAssets[style] + '-blue.svg';
            },
            applyFrameThumbnails: function () {
                var vm = this;
                Object.keys(frameAssets).forEach(function (style) {
                    var button = document.querySelector('#qrFrameStyles button[data-value="' + style + '"]');
                    if (!button) return;
                    var holder = button.querySelector('span');
                    holder.className = 'qr-svg-frame-thumb';
                    holder.innerHTML = '<img src="' + vm.frameUrl(style) + '" alt="">';
                });
            },
            setFrame: function (value) {
                document.getElementById('qrFrameSettings').classList.toggle('is-disabled', value === 'none');
                var dark = ['envelope','tray','sketch','beer','scooter','coffee','chef','pointing','plain_text','outline_text','receipt','script','arrow_script'];
                this.form.frameTextColor = dark.indexOf(value) !== -1 ? '#111827' : '#ffffff';
                this.el.frameTextColor.value = this.form.frameTextColor;
                document.getElementById('qrFrameTextColorText').textContent = this.form.frameTextColor.toUpperCase();
            },
            formData: function (download, frameOverride, sizeOverride) {
                var data = new FormData(), f = this.form;
                data.append('content', f.content.trim()); data.append('size', sizeOverride || f.size); data.append('level', f.level);
                data.append('foreground', f.foreground); data.append('background', f.background);
                data.append('module_style', f.moduleStyle); data.append('eye_style', f.eyeStyle);
                data.append('frame_style', frameOverride === undefined ? f.frameStyle : frameOverride); data.append('frame_text', f.frameText.trim());
                data.append('frame_color', f.frameColor); data.append('frame_text_color', f.frameTextColor);
                if (f.logo) data.append('logo', f.logo); data.append('download', download ? '1' : '0');
                return data;
            },
            schedule: function (delay) {
                var vm = this;
                if (this.previewTimer) clearTimeout(this.previewTimer);
                if (!this.form.content.trim()) { this.clearPreview(); this.showMessage('', ''); return; }
                if (this.previewController) this.previewController.abort();
                this.previewController = null; this.requestId++; this.el.preview.classList.add('is-loading');
                this.previewTimer = setTimeout(function () { vm.request(false, true); }, typeof delay === 'number' ? delay : 450);
            },
            request: function (download, automatic) {
                var vm = this;
                if (!this.form.content.trim()) { if (!automatic) { this.showMessage('Isi QR Code tidak boleh kosong.', 'error'); this.el.content.focus(); } return; }
                var id = download ? this.requestId : ++this.requestId;
                var vectorFrame = Object.prototype.hasOwnProperty.call(frameAssets, this.form.frameStyle);
                // Semua frame SVG menyusun QR mentah melalui renderer yang sama untuk preview dan PNG.
                var options = { method: 'POST', body: this.formData(vectorFrame ? false : download, vectorFrame ? 'none' : undefined), credentials: 'same-origin' };
                if (!download && window.AbortController) { if (this.previewController) this.previewController.abort(); this.previewController = new AbortController(); options.signal = this.previewController.signal; }
                if (!download) this.el.preview.classList.add('is-loading');
                if (!automatic && !download) { this.el.generate.disabled = true; this.el.generate.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Membuat...'; }
                this.showMessage('', '');
                fetch(window.qrCodeConfig.endpoint, options).then(function (response) {
                    if (!response.ok) return response.json().then(function (json) { throw new Error(json.message || 'QR Code gagal dibuat.'); });
                    return response.blob();
                }).then(async function (blob) {
                    if (vectorFrame) blob = await vm.composeSvgFrame(blob, vm.form.frameStyle);
                    if (download) { var link=document.createElement('a');link.href=URL.createObjectURL(blob);link.download='qr-code-'+new Date().toISOString().slice(0,10)+'.png';link.click();setTimeout(function(){URL.revokeObjectURL(link.href);},1000);return; }
                    if (id !== vm.requestId) return;
                    if (vm.previewUrl) URL.revokeObjectURL(vm.previewUrl); vm.previewUrl=URL.createObjectURL(blob);
                    vm.el.image.src=vm.previewUrl;vm.el.image.style.display='block';vm.el.empty.style.display='none';vm.el.download.disabled=false;
                }).catch(function (error) { if (error.name !== 'AbortError' && id === vm.requestId) { vm.clearPreview(); vm.showMessage(error.message, 'error'); } }).then(function () {
                    if (!download && id === vm.requestId) vm.el.preview.classList.remove('is-loading');
                    if (!automatic && !download) { vm.el.generate.disabled=false;vm.el.generate.innerHTML='<i class="fa fa-magic"></i> Perbarui QR Code'; }
                });
            },
            blobToDataUrl: function (blob) {
                return new Promise(function (resolve, reject) {
                    var reader = new FileReader();
                    reader.onload = function () { resolve(reader.result); };
                    reader.onerror = reject;
                    reader.readAsDataURL(blob);
                });
            },
            escapeXml: function (value) {
                return String(value || '').replace(/[&<>"']/g, function (character) {
                    return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&apos;'}[character];
                });
            },
            loadFrameSvg: async function (style) {
                if (this.frameSvgCache[style]) return this.frameSvgCache[style];
                var response = await fetch(this.frameUrl(style), {credentials:'same-origin'});
                if (!response.ok) throw new Error('Aset frame SVG tidak dapat dimuat.');
                var source = await response.text();
                this.frameSvgCache[style] = source;
                return source;
            },
            composeSvgFrame: async function (qrBlob, style) {
                var qrData = await this.blobToDataUrl(qrBlob), f = this.form;
                var source = await this.loadFrameSvg(style), parser = new DOMParser();
                var documentSvg = parser.parseFromString(source, 'image/svg+xml');
                if (documentSvg.querySelector('parsererror')) throw new Error('Struktur aset frame SVG tidak valid.');
                var root = documentSvg.documentElement, viewBox = (root.getAttribute('viewBox') || '0 0 1000 1300').trim().split(/\s+/).map(Number);
                var vbWidth = viewBox[2] || 1000, vbHeight = viewBox[3] || 1300;
                root.setAttribute('width', vbWidth); root.setAttribute('height', vbHeight);
                root.style.color = f.frameColor;

                // Tambahkan warna latar paling belakang tanpa mengubah path asli frame.
                var backdrop = documentSvg.createElementNS('http://www.w3.org/2000/svg', 'rect');
                backdrop.setAttribute('x', viewBox[0] || 0); backdrop.setAttribute('y', viewBox[1] || 0);
                backdrop.setAttribute('width', vbWidth); backdrop.setAttribute('height', vbHeight); backdrop.setAttribute('fill', f.background);
                root.insertBefore(backdrop, root.firstChild);
                Array.prototype.slice.call(root.querySelectorAll('rect')).forEach(function (rect) {
                    var width = parseFloat(rect.getAttribute('width')) || 0, height = parseFloat(rect.getAttribute('height')) || 0;
                    if (width >= vbWidth * .95 && height >= vbHeight * .95) {
                        rect.setAttribute('fill', f.background); rect.removeAttribute('style');
                    }
                });

                // Kotak abu-abu terbesar yang mendekati persegi adalah placeholder QR pada aset.
                var candidates = Array.prototype.slice.call(root.querySelectorAll('rect')).filter(function (rect) {
                    var width = parseFloat(rect.getAttribute('width')) || 0, height = parseFloat(rect.getAttribute('height')) || 0;
                    var fill = (rect.getAttribute('fill') || rect.getAttribute('style') || '').toLowerCase();
                    return width > vbWidth * .25 && height > vbWidth * .25 && Math.abs(width - height) < Math.max(width, height) * .08 &&
                        (fill.indexOf('e5e7ef') !== -1 || fill.indexOf('229, 231, 239') !== -1 || fill.indexOf('229,231,239') !== -1);
                }).sort(function (a, b) { return (parseFloat(b.getAttribute('width')) || 0) - (parseFloat(a.getAttribute('width')) || 0); });
                var placeholder = candidates[0];
                if (!placeholder) throw new Error('Area QR pada frame tidak ditemukan.');
                placeholder.setAttribute('fill', f.background); placeholder.removeAttribute('style');
                var px = parseFloat(placeholder.getAttribute('x')) || 0, py = parseFloat(placeholder.getAttribute('y')) || 0;
                var pw = parseFloat(placeholder.getAttribute('width')), ph = parseFloat(placeholder.getAttribute('height'));

                // SVG sumber masih mengandung QR contoh sebagai path. Tutup seluruh area
                // placeholder setelah semua path frame selesai digambar, baru timpa dengan QR aktual.
                // QR mentah sudah mempunyai quiet zone sendiri sehingga tidak perlu inset kedua.
                var qrCover = documentSvg.createElementNS('http://www.w3.org/2000/svg', 'rect');
                qrCover.setAttribute('x', px); qrCover.setAttribute('y', py);
                qrCover.setAttribute('width', pw); qrCover.setAttribute('height', ph);
                qrCover.setAttribute('fill', f.background);
                if (placeholder.hasAttribute('rx')) qrCover.setAttribute('rx', placeholder.getAttribute('rx'));
                if (placeholder.hasAttribute('ry')) qrCover.setAttribute('ry', placeholder.getAttribute('ry'));
                // Perbesar QR sedikit seperti proporsi QRFY. Clip menjaga hasil tetap
                // berada tepat di dalam area QR frame dan simetris di semua sisi.
                var defs = root.querySelector('defs');
                if (!defs) {
                    defs = documentSvg.createElementNS('http://www.w3.org/2000/svg', 'defs');
                    root.insertBefore(defs, root.firstChild);
                }
                var clipId = 'qr-frame-clip-' + String(style || 'plain').replace(/[^a-z0-9_-]/gi, '-');
                var clipPath = documentSvg.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
                clipPath.setAttribute('id', clipId);
                var clipRect = documentSvg.createElementNS('http://www.w3.org/2000/svg', 'rect');
                clipRect.setAttribute('x', px); clipRect.setAttribute('y', py);
                clipRect.setAttribute('width', pw); clipRect.setAttribute('height', ph);
                if (placeholder.hasAttribute('rx')) clipRect.setAttribute('rx', placeholder.getAttribute('rx'));
                if (placeholder.hasAttribute('ry')) clipRect.setAttribute('ry', placeholder.getAttribute('ry'));
                clipPath.appendChild(clipRect); defs.appendChild(clipPath);

                var qrScale = 1.23, qrExtraX = pw * (qrScale - 1) / 2, qrExtraY = ph * (qrScale - 1) / 2;
                var qrImage = documentSvg.createElementNS('http://www.w3.org/2000/svg', 'image');
                qrImage.setAttribute('x', px - qrExtraX); qrImage.setAttribute('y', py - qrExtraY);
                qrImage.setAttribute('width', pw * qrScale); qrImage.setAttribute('height', ph * qrScale);
                qrImage.setAttribute('clip-path', 'url(#' + clipId + ')');
                qrImage.setAttribute('preserveAspectRatio', 'xMidYMid meet'); qrImage.setAttribute('href', qrData);

                var firstText = root.querySelector('text');
                if (firstText) {
                    root.insertBefore(qrCover, firstText); root.insertBefore(qrImage, firstText);
                } else {
                    root.appendChild(qrCover); root.appendChild(qrImage);
                }
                Array.prototype.slice.call(root.querySelectorAll('text')).forEach(function (text) {
                    text.textContent = f.frameText.trim() || 'Scan me!'; text.setAttribute('fill', f.frameTextColor);
                    text.setAttribute('font-family', 'Arial,Helvetica,sans-serif');
                });

                var svg = new XMLSerializer().serializeToString(root);
                var svgBlob = new Blob([svg], {type:'image/svg+xml;charset=utf-8'});
                var svgUrl = URL.createObjectURL(svgBlob), width = Math.max(300, parseInt(f.size, 10) || 500);
                var height = Math.round(width * vbHeight / vbWidth), canvas = document.createElement('canvas');
                canvas.width = width; canvas.height = height;
                var image = new Image();
                try {
                    await new Promise(function (resolve, reject) { image.onload = resolve; image.onerror = reject; image.src = svgUrl; });
                    var context = canvas.getContext('2d', {alpha:false});
                    context.imageSmoothingEnabled = true; context.imageSmoothingQuality = 'high';
                    context.drawImage(image, 0, 0, width, height);
                    return await new Promise(function (resolve, reject) { canvas.toBlob(function (blob) { blob ? resolve(blob) : reject(new Error('Frame amplop gagal dirender.')); }, 'image/png', 1); });
                } finally {
                    URL.revokeObjectURL(svgUrl);
                }
            },
            clearPreview: function () {
                if (this.previewController) this.previewController.abort(); this.previewController=null;this.requestId++;
                if (this.previewTimer) clearTimeout(this.previewTimer);if(this.previewUrl)URL.revokeObjectURL(this.previewUrl);this.previewUrl=null;
                this.el.preview.classList.remove('is-loading');this.el.image.removeAttribute('src');this.el.image.style.display='none';this.el.empty.style.display='flex';this.el.download.disabled=true;
            },
            showMessage: function (text, type) { this.el.message.textContent=text||'';this.el.message.className='qr-message'+(text?' is-visible is-'+type:''); },
            updateLogo: function () {
                var file=this.el.logo.files&&this.el.logo.files[0];if(!file)return;
                if(file.size>2*1024*1024||!/^image\/(png|jpeg)$/.test(file.type)){this.showMessage('Logo harus JPG/PNG dan maksimal 2 MB.','error');this.el.logo.value='';return;}
                this.form.logo=file;this.showLogo(file);this.form.level='H';this.el.level.value='H';this.markActive('qrLevelOptions','H');this.schedule(100);
            },
            showLogo: function (file) { document.getElementById('qrLogoThumb').src=URL.createObjectURL(file);document.getElementById('qrLogoName').textContent=file.name;document.getElementById('qrLogoPlaceholder').style.display='none';document.getElementById('qrLogoSelected').style.display='flex';document.getElementById('qrRemoveLogo').style.display='inline-block'; },
            removeLogo: function () { this.form.logo=null;this.el.logo.value='';document.getElementById('qrLogoPlaceholder').style.display='flex';document.getElementById('qrLogoSelected').style.display='none';document.getElementById('qrRemoveLogo').style.display='none'; },
            reset: function () {
                this.form={content:'',size:'500',level:'M',foreground:'#111827',background:'#ffffff',moduleStyle:'square',eyeStyle:'square',frameStyle:'none',frameText:'Scan me!',frameColor:'#111827',frameTextColor:'#ffffff',logo:null};
                this.el.content.value='';this.el.size.value='500';this.el.level.value='M';this.el.foreground.value='#111827';this.el.background.value='#ffffff';this.el.frameText.value='Scan me!';this.el.frameColor.value='#111827';this.el.frameTextColor.value='#ffffff';
                document.getElementById('qrCharacterCount').textContent='0';['qrForegroundText','qrFrameColorText'].forEach(function(id){document.getElementById(id).textContent='#111827';});['qrBackgroundText','qrFrameTextColorText'].forEach(function(id){document.getElementById(id).textContent='#FFFFFF';});
                this.markActive('qrModuleStyles','square');this.markActive('qrEyeStyles','square');this.markActive('qrLevelOptions','M');this.markActive('qrFrameStyles','none');document.getElementById('qrFrameSettings').classList.add('is-disabled');this.removeLogo();this.clearPreview();this.showMessage('','');this.el.content.focus();
            }
        }
    }).mount('#qrCodeApp');
})();
