(function() {
    const SLOT_SELECTOR = '.report-image-handler-slot';
    const CROP_HANDLE_SIZE = 26;
    const CROP_HANDLE_HIT_SIZE = 30;
    const allowedTitleRe = /^[a-zA-Z0-9 _\-().]*$/;

    function slugifyForFilename(input) {
        let s = (input || '').trim();
        s = s.replace(/[^a-zA-Z0-9 _\-().]/g, '');
        s = s.replace(/\s+/g, '-');
        s = s.replace(/-+/g, '-').replace(/^-+|-+$/g, '');
        if (s.length > 60) s = s.slice(0, 60).replace(/-+$/g, '');
        return s;
    }

    function cleanTitleInput(raw) {
        return (raw || '').replace(/[^a-zA-Z0-9 _\-().]/g, '');
    }

    function imagePath(imageid, desc, thumb) {
        const cleanDesc = slugifyForFilename(desc || '');
        return 'images/report_images/' + imageid + (cleanDesc ? '__' + cleanDesc : '') + (thumb ? '_thumb' : '') + '.jpg';
    }

    function dataUrlToBlob(dataUrl) {
        const parts = dataUrl.split(',');
        const meta = parts[0] || '';
        const contentType = (meta.match(/data:([^;]+)/) || [])[1] || 'image/jpeg';
        const binary = atob(parts[1] || '');
        const bytes = new Uint8Array(binary.length);
        for (let i = 0; i < binary.length; i++) {
            bytes[i] = binary.charCodeAt(i);
        }
        return new Blob([bytes], { type: contentType });
    }

    function canvasToJpegBlob(canvas, quality) {
        return new Promise(function(resolve, reject) {
            if (canvas.toBlob) {
                canvas.toBlob(function(blob) {
                    if (blob) {
                        resolve(blob);
                        return;
                    }
                    reject(new Error('Image could not be exported.'));
                }, 'image/jpeg', quality);
                return;
            }

            try {
                resolve(dataUrlToBlob(canvas.toDataURL('image/jpeg', quality)));
            } catch (e) {
                reject(e);
            }
        });
    }

    function slotForImage(imageid, root) {
        return (root || document).querySelector(`${SLOT_SELECTOR}[data-meta-imageid="${imageid}"]`);
    }

    function setTitleState(imageid, hasImage) {
        const input = document.getElementById('imgDesc_' + imageid);
        const button = document.querySelector(`.report-image-title-save[data-imageid="${imageid}"], .report-image-title-save[data-imagebase="${imageid}"]`);

        if (input) {
            input.readOnly = !hasImage;
            input.setAttribute('data-has-image', hasImage ? '1' : '0');
            input.placeholder = hasImage ? 'Description (optional)' : 'Upload image first';
            if (!hasImage && input.value && input.getAttribute('data-current-desc') === '') {
                input.value = '';
            }
        }

        if (button) {
            button.disabled = !hasImage;
        }
    }

    window.syncReportImageTitleState = function(imageid) {
        if (!imageid) return false;
        const input = document.getElementById('imgDesc_' + imageid);
        const slot = slotForImage(imageid);
        const hasImage = (input && input.getAttribute('data-has-image') === '1') || (slot && !!slot.querySelector('img'));
        setTitleState(imageid, !!hasImage);
        return !!hasImage;
    };

    function getPreviewPath(imageid, desc, fallbackPath) {
        const cleanDesc = slugifyForFilename(desc || '');
        if (imageid) {
            return imagePath(imageid, cleanDesc, true);
        }

        if (fallbackPath && fallbackPath.indexOf('images/report_images/') !== -1) {
            const fullPath = fallbackPath.substring(fallbackPath.indexOf('images/report_images/'));
            return fullPath.replace(/\.jpg(\?.*)?$/i, '_thumb.jpg');
        }

        return fallbackPath ? fallbackPath.replace(/\.jpg(\?.*)?$/i, '_thumb.jpg') : '';
    }

    function refreshPreview(imageid, desc, fallbackPath) {
        const slot = slotForImage(imageid);
        const src = getPreviewPath(imageid, desc, fallbackPath);
        if (!slot || !src) return;

        const cache = (src.indexOf('?') === -1 ? '?' : '&') + 'v=' + Date.now();
        const previewSrc = encodeURI(src) + cache;
        const fullSrc = encodeURI(src.replace(/_thumb\.jpg$/i, '.jpg')) + cache;
        slot.querySelectorAll('img').forEach((img) => {
            img.src = previewSrc;
            img.setAttribute('data-full-src', fullSrc);
        });
    }

    async function renameImage(imageid, oldDesc, newDesc) {
        const post = new URLSearchParams();
        post.append('imageid', imageid);
        post.append('oldDesc', oldDesc || '');
        post.append('newDesc', newDesc || '');
        post.append('tenant', typeof TENANT_URL !== 'undefined' ? TENANT_URL : '');

        const res = await fetch('images/repimage_rename.php', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            body: post.toString()
        });

        const data = await res.json().catch(function() {
            return {};
        });
        if (!res.ok || !data.ok) {
            throw new Error(data && data.msg ? data.msg : 'Image title save failed');
        }
        return data;
    }

    function setTitleButton(button, state) {
        if (!button) return;
        button.disabled = state === 'saving';
        if (state === 'saving') {
            button.innerHTML = '<i class="fad fa-spinner fa-spin"></i>';
            return;
        }
        if (state === 'saved') {
            button.innerHTML = '<i class="fad fa-check"></i>';
            setTimeout(function() {
                button.innerHTML = '<i class="fad fa-save"></i>';
            }, 1200);
            return;
        }
        button.innerHTML = '<i class="fad fa-save"></i>';
    }

    async function saveTitle(imageid, button) {
        const input = document.getElementById('imgDesc_' + imageid);
        const slot = slotForImage(imageid);
        if (!input) return;

        const cleaned = cleanTitleInput(input.value || '');
        if (cleaned !== input.value) {
            input.value = cleaned;
            if (typeof showResponse === 'function') {
                showResponse('warning', 'Image title: invalid characters removed. Allowed: letters, numbers, spaces, _ - ( ) .');
            }
        }

        const newDesc = slugifyForFilename(input.value);
        const oldDesc = input.getAttribute('data-current-desc') || '';
        if (slot) {
            slot.setAttribute('data-meta-desc', newDesc);
        }

        const hasImage = input.getAttribute('data-has-image') === '1' || (slot && slot.querySelector('img'));
        if (!hasImage) {
            window.syncReportImageTitleState(imageid);
            if (typeof showResponse === 'function') {
                showResponse('warning', 'Upload an image before setting the title.');
            }
            return;
        }

        if (newDesc === oldDesc) {
            setTitleButton(button, 'saved');
            return;
        }

        try {
            setTitleButton(button, 'saving');
            const result = await renameImage(imageid, oldDesc, newDesc);
            input.setAttribute('data-current-desc', result.desc || '');
            input.setAttribute('data-has-image', '1');
            input.value = result.desc || '';
            if (slot) {
                slot.setAttribute('data-meta-desc', result.desc || '');
                refreshPreview(imageid, result.desc || newDesc, result.full || '');
            }
            setTitleButton(button, 'saved');
            if (typeof showResponse === 'function') {
                showResponse('success', 'Image title saved.');
            }
        } catch (e) {
            input.value = oldDesc;
            setTitleButton(button, 'idle');
            if (typeof showResponse === 'function') {
                showResponse('danger', e.message || 'Image title save failed.');
            }
        }
    }

    async function loadImageFile(fileInput, file) {
        if (!file || !file.type.match(/^image\//)) {
            throw new Error('Choose an image file.');
        }
        const slot = fileInput ? fileInput.closest(SLOT_SELECTOR) : null;
        if (!slot) return;

        const objectUrl = URL.createObjectURL(file);
        openEditor(slot, objectUrl, function() {
            URL.revokeObjectURL(objectUrl);
        });
    }

    function triggerPicker(slot, useCamera) {
        const targetInput = slot ? slot.querySelector('input[type="file"]') : null;
        if (!targetInput) return;

        const picker = document.createElement('input');
        picker.type = 'file';
        picker.accept = 'image/*';
        if (useCamera) {
            picker.setAttribute('capture', 'environment');
        }
        picker.style.position = 'fixed';
        picker.style.left = '-9999px';
        document.body.appendChild(picker);

        picker.addEventListener('change', async function(event) {
            event.preventDefault();
            event.stopPropagation();
            const file = picker.files && picker.files[0] ? picker.files[0] : null;
            if (!file) {
                picker.remove();
                return;
            }

            try {
                await loadImageFile(targetInput, file);
            } catch (e) {
                if (typeof showResponse === 'function') {
                    showResponse('danger', e.message || 'Image could not be prepared.');
                }
            } finally {
                picker.remove();
            }
        });

        picker.click();
    }

    function editImage(slot) {
        const img = slot ? slot.querySelector('img') : null;
        if (!img || !img.src) {
            if (typeof showResponse === 'function') {
                showResponse('warning', 'Select an image before editing.');
            }
            return;
        }
        const fullSrc = img.getAttribute('data-full-src') || img.src.replace(/_thumb\.jpg(\?.*)?$/i, '.jpg$1');
        openEditor(slot, fullSrc);
    }

    function setPreview(slot, src, fullSrc) {
        slot.querySelectorAll('.report-image-empty').forEach((empty) => empty.remove());
        let img = slot.querySelector('img');
        if (!img) {
            img = document.createElement('img');
            img.alt = 'Report Image';
            slot.insertBefore(img, slot.firstChild);
        }
        img.src = src;
        if (fullSrc) {
            img.setAttribute('data-full-src', fullSrc);
        }
    }

    async function uploadImage(slot, blob) {
        const imageid = slot.getAttribute('data-meta-imageid') || '';
        const userid = slot.getAttribute('data-meta-userid') || '';
        const input = document.getElementById('imgDesc_' + imageid);
        const desc = slugifyForFilename(input ? input.value : '');
        const post = new FormData();
        post.append('file', JSON.stringify({
            meta: {
                userid: userid,
                imageid: imageid,
                desc: desc,
                tenant: typeof TENANT_URL !== 'undefined' ? TENANT_URL : ''
            }
        }));
        post.append('image', blob, 'report-image.jpg');

        const res = await fetch('images/repimage_save.php', {
            method: 'POST',
            body: post
        });
        if (!res.ok) {
            throw new Error('Image upload failed.');
        }

        slot.setAttribute('data-meta-desc', desc);
        const cache = '?v=' + Date.now();
        setPreview(slot, encodeURI(imagePath(imageid, desc, true)) + cache, encodeURI(imagePath(imageid, desc, false)) + cache);
        if (input) {
            input.readOnly = false;
            input.setAttribute('data-has-image', '1');
            input.setAttribute('data-current-desc', desc);
        }
        window.syncReportImageTitleState(imageid);
    }

    function getEditor() {
        let editor = document.getElementById('reportImageEditor');
        if (editor) return editor;

        editor = document.createElement('div');
        editor.id = 'reportImageEditor';
        editor.className = 'report-image-editor';
        editor.innerHTML = `
            <div class="report-image-editor-head">
                <strong>Edit Image</strong><br><small>Drag or resize the crop box.</small>
            </div>
            <div class="report-image-editor-stage">
                <div class="report-image-editor-loading">
                    <i class="fad fa-spinner fa-spin"></i>
                    <span>Loading image</span>
                </div>
                <canvas class="report-image-editor-canvas"></canvas>
            </div>
            <div class="report-image-editor-actions">
                <select class="form-select form-select-sm w-auto report-image-editor-constraint" data-editor-action="constraint" title="Crop constraint">
                    <option value="">No constraint</option>
                    <option value="1">Square</option>
                    <option value="1.5">Landscape 6:4</option>
                    <option value="0.6666666667">Portrait 4:6</option>
                </select>
                <button type="button" class="btn btn-light btn-sm" data-editor-action="rotate-left"><i class="fad fa-undo"></i></button>
                <button type="button" class="btn btn-light btn-sm" data-editor-action="rotate-right"><i class="fad fa-redo"></i></button>
                <button type="button" class="btn btn-light btn-sm" data-editor-action="reset-crop"><i class="fad fa-expand-arrows-alt"></i></button>
                <button type="button" class="btn btn-success btn-sm" data-editor-action="save"><i class="fad fa-save"></i></button>
                <button type="button" class="btn btn-secondary btn-sm" data-editor-action="cancel"><i class="fad fa-times"></i></button>
            </div>`;
        document.body.appendChild(editor);
        return editor;
    }

    function openEditor(slot, src, cleanup) {
        const editor = getEditor();
        const canvas = editor.querySelector('canvas');
        const loading = editor.querySelector('.report-image-editor-loading');
        const image = new Image();
        const state = {
            rotation: 0,
            imageRect: null,
            crop: null,
            aspect: null,
            dragMode: '',
            dragging: false,
            lastX: 0,
            lastY: 0,
            startX: 0,
            startY: 0,
            startCrop: null,
            cleanup: cleanup || function() {}
        };
        let saveButton = editor.querySelector('[data-editor-action="save"]');
        if (saveButton) saveButton.disabled = true;
        if (loading) loading.style.display = 'flex';
        canvas.style.visibility = 'hidden';

        function sizeCanvas() {
            const viewport = window.visualViewport || window;
            const width = Math.max(280, Math.floor(viewport.width || window.innerWidth) - 16);
            const height = Math.max(260, Math.floor(viewport.height || window.innerHeight) - 136);
            canvas.width = width;
            canvas.height = height;
            canvas.style.width = width + 'px';
            canvas.style.height = height + 'px';
        }

        function imageRect(cw, ch) {
            const rotated = Math.abs(state.rotation % 180) === 90;
            const iw = rotated ? image.height : image.width;
            const ih = rotated ? image.width : image.height;
            const scale = Math.min(cw / iw, ch / ih);
            const w = iw * scale;
            const h = ih * scale;
            return {
                x: (cw - w) / 2,
                y: (ch - h) / 2,
                w: w,
                h: h,
                scale: scale
            };
        }

        function resetCrop() {
            state.imageRect = imageRect(canvas.width, canvas.height);
            const maxW = Math.max(60, state.imageRect.w);
            const maxH = Math.max(60, state.imageRect.h);
            let cropW = maxW;
            let cropH = maxH;

            if (state.aspect) {
                const fitted = fitSizeToAspect(maxW, maxH, state.aspect);
                cropW = fitted.w;
                cropH = fitted.h;
            }

            state.crop = {
                x: state.imageRect.x + Math.max(0, (state.imageRect.w - cropW) / 2),
                y: state.imageRect.y + Math.max(0, (state.imageRect.h - cropH) / 2),
                w: cropW,
                h: cropH
            };
        }

        function drawBase(targetCanvas) {
            const targetCtx = targetCanvas.getContext('2d');
            const cw = targetCanvas.width;
            const ch = targetCanvas.height;
            const rect = imageRect(cw, ch);
            targetCtx.fillStyle = '#000';
            targetCtx.fillRect(0, 0, cw, ch);
            targetCtx.save();
            targetCtx.translate(cw / 2, ch / 2);
            targetCtx.rotate(state.rotation * Math.PI / 180);
            targetCtx.drawImage(image, -image.width * rect.scale / 2, -image.height * rect.scale / 2, image.width * rect.scale, image.height * rect.scale);
            targetCtx.restore();
        }

        function handles() {
            const c = state.crop;
            return [
                { name: 'nw', x: c.x, y: c.y },
                { name: 'n', x: c.x + c.w / 2, y: c.y },
                { name: 'ne', x: c.x + c.w, y: c.y },
                { name: 'e', x: c.x + c.w, y: c.y + c.h / 2 },
                { name: 'se', x: c.x + c.w, y: c.y + c.h },
                { name: 's', x: c.x + c.w / 2, y: c.y + c.h },
                { name: 'sw', x: c.x, y: c.y + c.h },
                { name: 'w', x: c.x, y: c.y + c.h / 2 }
            ];
        }

        function render() {
            if (!state.crop) resetCrop();
            drawBase(canvas);
            const ctx = canvas.getContext('2d');
            ctx.save();
            ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
            ctx.fillRect(0, 0, canvas.width, state.crop.y);
            ctx.fillRect(0, state.crop.y + state.crop.h, canvas.width, canvas.height - state.crop.y - state.crop.h);
            ctx.fillRect(0, state.crop.y, state.crop.x, state.crop.h);
            ctx.fillRect(state.crop.x + state.crop.w, state.crop.y, canvas.width - state.crop.x - state.crop.w, state.crop.h);
            ctx.strokeStyle = '#ffffff';
            ctx.lineWidth = 2;
            ctx.strokeRect(state.crop.x, state.crop.y, state.crop.w, state.crop.h);
            ctx.fillStyle = '#ffffff';
            handles().forEach(function(handle) {
                const half = CROP_HANDLE_SIZE / 2;
                ctx.fillRect(handle.x - half, handle.y - half, CROP_HANDLE_SIZE, CROP_HANDLE_SIZE);
            });
            ctx.restore();
        }

        function closeEditor() {
            editor.classList.remove('is-open');
            document.body.classList.remove('report-image-editor-open');
            window.removeEventListener('resize', handleResize);
            if (window.visualViewport) {
                window.visualViewport.removeEventListener('resize', handleResize);
            }
            state.cleanup();
        }

        function handleResize() {
            sizeCanvas();
            resetCrop();
            render();
        }

        function point(event) {
            const rect = canvas.getBoundingClientRect();
            return {
                x: (event.clientX - rect.left) * (canvas.width / rect.width),
                y: (event.clientY - rect.top) * (canvas.height / rect.height)
            };
        }

        function hitTest(p) {
            for (const handle of handles()) {
                if (Math.abs(p.x - handle.x) <= CROP_HANDLE_HIT_SIZE && Math.abs(p.y - handle.y) <= CROP_HANDLE_HIT_SIZE) return handle.name;
            }
            const c = state.crop;
            if (p.x >= c.x && p.x <= c.x + c.w && p.y >= c.y && p.y <= c.y + c.h) return 'move';
            return '';
        }

        function fitSizeToAspect(maxW, maxH, aspect) {
            let w = maxW;
            let h = w / aspect;
            if (h > maxH) {
                h = maxH;
                w = h * aspect;
            }
            return { w: w, h: h };
        }

        function maxAspectSize() {
            return fitSizeToAspect(state.imageRect.w, state.imageRect.h, state.aspect);
        }

        function constrainAspectCrop(anchorX, anchorY, keepRight, keepBottom) {
            const c = state.crop;
            const max = maxAspectSize();
            const minW = Math.min(40, max.w);
            const minH = minW / state.aspect;

            if (c.w < minW) {
                c.w = minW;
                c.h = minH;
            }
            if (c.w > max.w) {
                c.w = max.w;
                c.h = max.h;
            }

            c.h = c.w / state.aspect;
            if (c.h > max.h) {
                c.h = max.h;
                c.w = c.h * state.aspect;
            }

            if (keepRight) {
                c.x = anchorX - c.w;
            }
            if (keepBottom) {
                c.y = anchorY - c.h;
            }
            clampCrop();
        }

        function clampCrop() {
            const r = state.imageRect;
            const c = state.crop;
            const min = 40;
            if (state.aspect) {
                const max = maxAspectSize();
                if (c.w < min) c.w = Math.min(min, max.w);
                c.h = c.w / state.aspect;
                if (c.w > max.w) {
                    c.w = max.w;
                    c.h = max.h;
                }
                if (c.h > max.h) {
                    c.h = max.h;
                    c.w = c.h * state.aspect;
                }
            } else {
                if (c.w < min) c.w = min;
                if (c.h < min) c.h = min;
            }
            if (c.x < r.x) c.x = r.x;
            if (c.y < r.y) c.y = r.y;
            if (c.x + c.w > r.x + r.w) c.x = r.x + r.w - c.w;
            if (c.y + c.h > r.y + r.h) c.y = r.y + r.h - c.h;
            if (c.w > r.w) {
                c.x = r.x;
                c.w = r.w;
            }
            if (c.h > r.h) {
                c.y = r.y;
                c.h = r.h;
            }
        }

        function updateConstrainedCrop(p) {
            const mode = state.dragMode;
            const start = state.startCrop;
            const aspect = state.aspect;
            const c = state.crop;
            if (!start) return;

            if (mode === 'move') {
                c.x = start.x + (p.x - state.startX);
                c.y = start.y + (p.y - state.startY);
                c.w = start.w;
                c.h = start.h;
                clampCrop();
                return;
            }

            if (mode === 'n' || mode === 's' || mode === 'e' || mode === 'w') {
                const cx = start.x + start.w / 2;
                const cy = start.y + start.h / 2;
                let w = start.w;
                let h = start.h;
                if (mode === 'e' || mode === 'w') {
                    w = Math.abs(p.x - cx) * 2;
                    h = w / aspect;
                } else {
                    h = Math.abs(p.y - cy) * 2;
                    w = h * aspect;
                }
                c.w = w;
                c.h = h;
                c.x = cx - w / 2;
                c.y = cy - h / 2;
                clampCrop();
                return;
            }

            const anchorX = mode.indexOf('w') !== -1 ? start.x + start.w : start.x;
            const anchorY = mode.indexOf('n') !== -1 ? start.y + start.h : start.y;
            const keepRight = mode.indexOf('w') !== -1;
            const keepBottom = mode.indexOf('n') !== -1;
            const desiredW = Math.max(1, Math.abs(p.x - anchorX));
            const desiredH = Math.max(1, Math.abs(p.y - anchorY));
            let w = desiredW;
            let h = w / aspect;
            if (desiredH > h) {
                h = desiredH;
                w = h * aspect;
            }

            c.w = w;
            c.h = h;
            c.x = keepRight ? anchorX - w : anchorX;
            c.y = keepBottom ? anchorY - h : anchorY;
            constrainAspectCrop(anchorX, anchorY, keepRight, keepBottom);
        }

        function updateCrop(dx, dy) {
            const c = state.crop;
            const mode = state.dragMode;
            if (mode === 'move') {
                c.x += dx;
                c.y += dy;
            } else {
                if (mode.indexOf('w') !== -1) {
                    c.x += dx;
                    c.w -= dx;
                }
                if (mode.indexOf('e') !== -1) c.w += dx;
                if (mode.indexOf('n') !== -1) {
                    c.y += dy;
                    c.h -= dy;
                }
                if (mode.indexOf('s') !== -1) c.h += dy;
            }
            clampCrop();
        }

        function setConstraint(value) {
            state.aspect = value ? parseFloat(value) : null;
            resetCrop();
            render();
        }

        editor.onclick = function(event) {
            const actionEl = event.target.closest ? event.target.closest('[data-editor-action]') : null;
            const action = actionEl ? actionEl.getAttribute('data-editor-action') : '';
            if (!action) return;
            if (action === 'constraint') return;
            event.preventDefault();
            if (action === 'cancel') {
                closeEditor();
                return;
            }
            if (action === 'reset-crop') resetCrop();
            if (action === 'rotate-left') {
                state.rotation = (state.rotation - 90) % 360;
                resetCrop();
            }
            if (action === 'rotate-right') {
                state.rotation = (state.rotation + 90) % 360;
                resetCrop();
            }
            if (action === 'save') {
                saveButton = actionEl;
                if (!state.crop) {
                    if (typeof showResponse === 'function') {
                        showResponse('warning', 'Image is still loading.');
                    }
                    return;
                }
                if (saveButton && saveButton.disabled) return;
                if (saveButton) saveButton.disabled = true;
                const source = document.createElement('canvas');
                source.width = canvas.width;
                source.height = canvas.height;
                drawBase(source);
                const out = document.createElement('canvas');
                const maxSide = 1024;
                const ratio = state.crop.w / state.crop.h;
                const outW = ratio >= 1 ? maxSide : Math.round(maxSide * ratio);
                const outH = ratio >= 1 ? Math.round(maxSide / ratio) : maxSide;
                out.width = outW;
                out.height = outH;
                out.getContext('2d').drawImage(source, state.crop.x, state.crop.y, state.crop.w, state.crop.h, 0, 0, outW, outH);
                canvasToJpegBlob(out, 0.9).then(async function(blob) {
                    try {
                        await uploadImage(slot, blob);
                        closeEditor();
                        if (typeof showResponse === 'function') {
                            showResponse('success', 'Image saved.');
                        }
                    } catch (e) {
                        if (typeof showResponse === 'function') {
                            showResponse('danger', e.message || 'Image save failed.');
                        }
                    } finally {
                        if (saveButton) saveButton.disabled = false;
                    }
                }).catch(function(e) {
                    if (saveButton) saveButton.disabled = false;
                    if (typeof showResponse === 'function') {
                        showResponse('danger', e.message || 'Image could not be exported.');
                    }
                });
                return;
            }
            render();
        };

        editor.onchange = function(event) {
            if (!event.target.matches('.report-image-editor-constraint')) return;
            setConstraint(event.target.value);
        };

        canvas.onpointerdown = function(event) {
            event.preventDefault();
            const p = point(event);
            const mode = hitTest(p);
            if (!mode) return;
            state.dragging = true;
            state.dragMode = mode;
            state.lastX = p.x;
            state.lastY = p.y;
            state.startX = p.x;
            state.startY = p.y;
            state.startCrop = Object.assign({}, state.crop);
            canvas.setPointerCapture(event.pointerId);
        };
        canvas.onpointermove = function(event) {
            if (!state.dragging) return;
            event.preventDefault();
            const p = point(event);
            if (state.aspect) {
                updateConstrainedCrop(p);
            } else {
                updateCrop(p.x - state.lastX, p.y - state.lastY);
            }
            state.lastX = p.x;
            state.lastY = p.y;
            render();
        };
        canvas.onpointerup = function() {
            state.dragging = false;
            state.dragMode = '';
            state.startCrop = null;
        };

        image.onload = function() {
            const constraint = editor.querySelector('.report-image-editor-constraint');
            if (constraint) constraint.value = '';
            state.aspect = null;
            sizeCanvas();
            resetCrop();
            if (saveButton) saveButton.disabled = false;
            if (loading) loading.style.display = 'none';
            canvas.style.visibility = 'visible';
            window.addEventListener('resize', handleResize);
            if (window.visualViewport) {
                window.visualViewport.addEventListener('resize', handleResize);
            }
            render();
        };
        image.onerror = function() {
            closeEditor();
            if (typeof showResponse === 'function') {
                showResponse('danger', 'Image could not be opened.');
            }
        };
        editor.classList.add('is-open');
        document.body.classList.add('report-image-editor-open');
        image.src = src;
    }

    async function removeImage(slot) {
        const img = slot ? slot.querySelector('img') : null;
        const imageid = slot ? slot.getAttribute('data-meta-imageid') : '';
        if (!img || !imageid) {
            if (typeof showResponse === 'function') {
                showResponse('warning', 'No image to remove.');
            }
            return;
        }

        const imageName = decodeURIComponent((img.src.split('/').pop() || '').split('?')[0]);
        await fetch('images/repimage_remove.php', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            body: new URLSearchParams({ image: imageName })
        });

        img.remove();
        slot.insertAdjacentHTML('afterbegin', '<div class="report-image-empty">Drop image here or use the buttons below</div>');
        const input = document.getElementById('imgDesc_' + imageid);
        if (input) {
            input.readOnly = true;
            input.setAttribute('data-has-image', '0');
            input.setAttribute('data-current-desc', '');
        }
        window.syncReportImageTitleState(imageid);
        if (typeof showResponse === 'function') {
            showResponse('success', 'Image removed successfully');
        }
    }

    function confirmRemove(slot) {
        const img = slot ? slot.querySelector('img') : null;
        if (!img) {
            if (typeof showResponse === 'function') {
                showResponse('warning', 'No image to remove.');
            }
            return;
        }

        const html = '<p class="mb-0 text-center">Are you sure you want to remove this image?</p>';
        loadModalContent(html, 'reportImageRemoveConfirm', 'modal-sm', 1060, 'true', 'Remove Image', 'cancelConfirm', function() {
            if (typeof killModal === 'function') {
                killModal('reportImageRemoveConfirm');
            }
            removeImage(slot).catch(console.error);
        });
    }

    function bindSlots(root) {
        (root || document).querySelectorAll('.report-image-card').forEach((card) => {
            if (card.getAttribute('data-report-image-bound') === '1') return;
            card.setAttribute('data-report-image-bound', '1');
            const slot = card.querySelector(SLOT_SELECTOR);
            const fileInput = slot ? slot.querySelector('input[type="file"]') : null;

            card.querySelectorAll('.report-image-camera').forEach((button) => {
                button.addEventListener('click', function(event) {
                    event.preventDefault();
                    event.stopPropagation();
                    triggerPicker(slot, true);
                });
            });
            card.querySelectorAll('.report-image-browse').forEach((button) => {
                button.addEventListener('click', function(event) {
                    event.preventDefault();
                    event.stopPropagation();
                    triggerPicker(slot, false);
                });
            });
            card.querySelectorAll('.report-image-edit').forEach((button) => {
                button.addEventListener('click', function(event) {
                    event.preventDefault();
                    event.stopPropagation();
                    editImage(slot);
                });
            });
            card.querySelectorAll('.report-image-remove').forEach((button) => {
                button.addEventListener('click', function(event) {
                    event.preventDefault();
                    event.stopPropagation();
                    confirmRemove(slot);
                });
            });

            if (!slot || !fileInput) return;
            ['dragenter', 'dragover'].forEach((eventName) => {
                slot.addEventListener(eventName, function(event) {
                    event.preventDefault();
                    event.stopPropagation();
                    slot.classList.add('report-image-dragover');
                });
            });
            ['dragleave', 'drop'].forEach((eventName) => {
                slot.addEventListener(eventName, function() {
                    slot.classList.remove('report-image-dragover');
                });
            });
            slot.addEventListener('drop', function(event) {
                event.preventDefault();
                event.stopPropagation();
                const files = event.dataTransfer && event.dataTransfer.files ? event.dataTransfer.files : null;
                if (!files || !files.length || !files[0].type.match(/^image\//)) {
                    if (typeof showResponse === 'function') {
                        showResponse('warning', 'Drop an image file into the slot.');
                    }
                    return;
                }
                loadImageFile(fileInput, files[0]).catch(function(e) {
                    if (typeof showResponse === 'function') {
                        showResponse('danger', e.message || 'Image could not be prepared.');
                    }
                });
            });
        });
    }

    function bindTitles(root) {
        (root || document).querySelectorAll('.report-image-desc').forEach((input) => {
            if (input.getAttribute('data-report-image-title-bound') === '1') return;
            input.setAttribute('data-report-image-title-bound', '1');
            const imageid = input.getAttribute('data-imageid') || input.getAttribute('data-imagebase') || '';
            const slot = slotForImage(imageid, root);
            if (slot) {
                slot.setAttribute('data-meta-desc', slugifyForFilename(input.value));
                if (window.MutationObserver) {
                    const observer = new MutationObserver(function() {
                        window.syncReportImageTitleState(imageid);
                    });
                    observer.observe(slot, { childList: true, subtree: true, attributes: true });
                }
            }
            window.syncReportImageTitleState(imageid);
            input.addEventListener('input', function() {
                if (!allowedTitleRe.test(input.value || '')) {
                    input.value = cleanTitleInput(input.value || '');
                    if (typeof showResponse === 'function') {
                        showResponse('warning', 'Image title: invalid characters removed. Allowed: letters, numbers, spaces, _ - ( ) .');
                    }
                }
                if (slot) {
                    slot.setAttribute('data-meta-desc', slugifyForFilename(input.value));
                }
            });
            input.addEventListener('keydown', function(event) {
                if (event.key === 'Enter') {
                    event.preventDefault();
                    const button = document.querySelector(`.report-image-title-save[data-imageid="${imageid}"], .report-image-title-save[data-imagebase="${imageid}"]`);
                    saveTitle(imageid, button);
                }
            });
        });

        (root || document).querySelectorAll('.report-image-title-save').forEach((button) => {
            if (button.getAttribute('data-report-image-title-bound') === '1') return;
            button.setAttribute('data-report-image-title-bound', '1');
            button.addEventListener('click', function(event) {
                event.preventDefault();
                saveTitle(button.getAttribute('data-imageid') || button.getAttribute('data-imagebase') || '', button);
            });
        });
    }

    function init(root) {
        bindTitles(root || document);
        bindSlots(root || document);
    }

    window.EnrepReportImageHandler = {
        init: init,
        syncTitleState: window.syncReportImageTitleState
    };

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', function() {
            init(document);
        });
    } else {
        init(document);
    }
})();
