/*
 * Bericht-Editor — PDF.js + Fabric.js + SortableJS
 * Lädt Seiten via /ajax/page_image.php (Bilder direkt, PDFs via PDF.js gerendert),
 * legt Fabric.js-Canvas darüber, speichert Annotationen pro Seite über Ajax.
 *
 * Erwartet im DOM:
 *   #pdf-canvas         — Canvas für die Seitendarstellung
 *   #fabric-canvas      — Overlay-Canvas für Annotationen
 *   #bericht-page-list  — Container für Seiten-Thumbnails (.page-thumb[data-pageid])
 *   #page-note          — Textarea für Seiten-Notiz
 *   .att-check          — Checkboxen der Anhänge-Liste
 *   #btn-add-selected   — Button "Auswahl in Bericht übernehmen"
 *   #btn-save-draft     — Entwurf speichern
 *   #btn-finalize       — Bericht finalisieren
 *   #btn-undo / #btn-redo / #btn-delete-selected
 *   .tool-btn[data-tool]
 *   #tool-color, #tool-stroke
 *   #bericht-extra-upload (file input)
 *
 * Globale Konfiguration: window.BERICHT_CONFIG (vom PHP gesetzt)
 */

(function () {
    'use strict';

    const cfg = window.BERICHT_CONFIG || {};
    if (!cfg.berichtid) { console.warn('Bericht: keine Konfiguration'); return; }

    /**
     * Liest den Notiz-Text aus — funktioniert mit CKEditor (DolEditor) UND plain textarea
     */
    function getNoteValue() {
        if (window.CKEDITOR && window.CKEDITOR.instances && window.CKEDITOR.instances['page-note']) {
            return window.CKEDITOR.instances['page-note'].getData() || '';
        }
        const el = document.getElementById('page-note');
        return el ? el.value : '';
    }

    /**
     * Setzt den Notiz-Text.
     */
    function setNoteValue(html) {
        if (window.CKEDITOR && window.CKEDITOR.instances && window.CKEDITOR.instances['page-note']) {
            window.CKEDITOR.instances['page-note'].setData(html || '');
            return;
        }
        const el = document.getElementById('page-note');
        if (el) el.value = html || '';
    }

    // PDF.js worker (lokal)
    if (window.pdfjsLib) {
        pdfjsLib.GlobalWorkerOptions.workerSrc = '/bericht/js/lib/pdf.worker.min.js';
    }

    let currentPageId = null;
    let currentPageEl = null;
    let currentPageRotation = 0;          // 0 / 90 / 180 / 270
    let currentPageBuffer = null;         // ArrayBuffer der aktuellen Quelle
    let currentPageMime = '';
    let currentPageLayout = 'single';
    let currentPageScale = 1.0;
    let currentPageAlign = 'fit';
    let currentZoom = 1.0;                // 1.0 = 100% (Container-Fit), 0.5..3.0
    let fabricCanvas = null;
    const pdfCanvas = document.getElementById('pdf-canvas');
    let currentTool = 'select';

    /* ---------- Settings-Persistenz (localStorage) ---------- */
    const SETTINGS_KEY = 'bericht.editor.settings.v1';
    function loadSettings() {
        try {
            const raw = localStorage.getItem(SETTINGS_KEY);
            if (!raw) return {};
            return JSON.parse(raw) || {};
        } catch (e) { return {}; }
    }
    function saveSettings(patch) {
        try {
            const cur = loadSettings();
            const merged = Object.assign({}, cur, patch);
            localStorage.setItem(SETTINGS_KEY, JSON.stringify(merged));
        } catch (e) { /* localStorage full/blocked */ }
    }

    /* ---------- Bild-Viewer für Anhänge ---------- */
    function bindImageViewer() {
        // Prüfen ob BerichtImageViewer geladen ist
        if (typeof window.BerichtImageViewer === 'undefined') {
            console.warn('BerichtImageViewer nicht geladen');
            return;
        }

        const viewer = new window.BerichtImageViewer();
        const previews = document.querySelectorAll('.att-preview');

        if (previews.length === 0) return;

        // Bilder pro Gruppe sammeln (für Navigation innerhalb der Gruppe)
        const imagesByGroup = {};
        previews.forEach((el) => {
            const group = el.dataset.group || 'default';
            if (!imagesByGroup[group]) imagesByGroup[group] = [];
            imagesByGroup[group].push({
                url: el.dataset.url,
                title: el.dataset.filename || ''
            });
        });

        // Klick-Handler
        previews.forEach((el) => {
            el.addEventListener('click', (e) => {
                e.preventDefault();
                e.stopPropagation();

                const group = el.dataset.group || 'default';
                const images = imagesByGroup[group] || [];
                const url = el.dataset.url;

                // Index in der Gruppe finden
                let startIndex = images.findIndex(img => img.url === url);
                if (startIndex < 0) startIndex = 0;

                viewer.open(images, startIndex);
            });
        });
    }

    /* ---------- Init ---------- */
    function init() {
        // Leer-Zustand nur wenn GAR keine Seite existiert
        const wrapEmpty = document.querySelector('.bericht-canvas-wrap');
        const hasAnyThumb = !!document.querySelector('#bericht-page-list .page-thumb');
        if (wrapEmpty && !hasAnyThumb) wrapEmpty.classList.add('empty');

        // Gespeicherte Einstellungen anwenden — VOR Fabric-Init
        const s = loadSettings();
        const colorEl = document.getElementById('tool-color');
        const strokeEl = document.getElementById('tool-stroke');
        const ffEl = document.getElementById('tool-fontfamily');
        const fsEl = document.getElementById('tool-fontsize');
        const boldEl = document.getElementById('tool-bold');
        const italicEl = document.getElementById('tool-italic');

        if (s.color)      colorEl.value      = s.color;
        if (s.stroke)     strokeEl.value     = s.stroke;
        if (s.fontFamily) ffEl.value         = s.fontFamily;
        if (s.fontSize)   fsEl.value         = s.fontSize;
        if (typeof s.bold !== 'undefined')   boldEl.checked   = !!s.bold;
        if (typeof s.italic !== 'undefined') italicEl.checked = !!s.italic;
        if (s.zoom)       currentZoom        = parseFloat(s.zoom) || 1.0;
        document.getElementById('zoom-label').textContent = Math.round(currentZoom * 100) + '%';

        // Fabric initialisieren (wird beim ersten Seitenrendern dimensioniert)
        fabricCanvas = new fabric.Canvas('fabric-canvas', {
            isDrawingMode: false,
            selection: true,
        });
        fabricCanvas.freeDrawingBrush.color = colorEl.value;
        fabricCanvas.freeDrawingBrush.width = parseInt(strokeEl.value, 10);

        // Listener: speichern bei jeder Änderung
        colorEl.addEventListener('change', () => saveSettings({ color: colorEl.value }));
        strokeEl.addEventListener('change', () => saveSettings({ stroke: parseInt(strokeEl.value, 10) }));
        ffEl.addEventListener('change', () => saveSettings({ fontFamily: ffEl.value }));
        fsEl.addEventListener('change', () => saveSettings({ fontSize: parseInt(fsEl.value, 10) }));
        boldEl.addEventListener('change', () => saveSettings({ bold: boldEl.checked }));
        italicEl.addEventListener('change', () => saveSettings({ italic: italicEl.checked }));

        // Erste Seite laden (wenn vorhanden)
        const firstThumb = document.querySelector('#bericht-page-list .page-thumb');
        if (firstThumb) loadPage(firstThumb);

        // Alle Thumbnails parallel rendern
        renderAllThumbs();

        bindThumbs();
        bindToolbar();
        bindAttachments();
        bindExtraUpload();
        bindActions();
        bindSortable();
        bindImageViewer();

        // Re-Render bei Größenänderung des Container (Console öffnen, Window-Resize),
        // debounced damit es nicht spamt. Nutzt ResizeObserver auf canvas-wrap.
        const wrap = document.querySelector('.bericht-canvas-wrap');
        if (wrap && typeof ResizeObserver !== 'undefined') {
            let to = null;
            let lastW = wrap.clientWidth;
            const ro = new ResizeObserver(() => {
                if (Math.abs(wrap.clientWidth - lastW) < 20) return;
                lastW = wrap.clientWidth;
                clearTimeout(to);
                to = setTimeout(() => { rerenderCurrent(); }, 250);
            });
            ro.observe(wrap);
        }
    }

    /* ---------- Seiten laden ---------- */
    async function loadPage(thumbEl) {
        // vorher: aktuelle Seite speichern
        if (currentPageId) await savePageAnnotations(false);

        currentPageEl = thumbEl;
        currentPageId = parseInt(thumbEl.dataset.pageid, 10);

        document.querySelectorAll('.page-thumb.active').forEach(e => e.classList.remove('active'));
        thumbEl.classList.add('active');

        const url = cfg.urls.page_image + '?pageid=' + currentPageId;
        const resp = await fetch(url);
        const ct = resp.headers.get('Content-Type') || '';
        const buf = await resp.arrayBuffer();

        currentPageBuffer = buf;
        currentPageMime = ct;
        currentPageRotation = 0;

        // Empty-Status beenden — Canvas bekommt jetzt echten Inhalt
        const wrapL = document.querySelector('.bericht-canvas-wrap');
        if (wrapL) wrapL.classList.remove('empty'); // wird gleich aus loadPageMeta überschrieben falls gespeichert

        fabricCanvas.clear();
        setNoteValue('');

        await loadPageMeta();        // setzt currentPageRotation + ggf. loadedFabricJson

        // Canvas-Dimensionen IMMER auf A4 setzen
        const target = getTargetCanvasWidth();
        const pageAspect = 1 / 1.414;
        const isLandscape = currentPageRotation === 90 || currentPageRotation === 270;
        pdfCanvas.width = target;
        pdfCanvas.height = isLandscape ? Math.round(target * pageAspect) : Math.round(target / pageAspect);
        const pctx = pdfCanvas.getContext('2d');
        pctx.fillStyle = '#ffffff';
        pctx.fillRect(0, 0, pdfCanvas.width, pdfCanvas.height);
        resizeFabricToCanvas();

        // Quellbild IMMER frisch laden — gespeicherte bgImage-Objekte haben eine
        // blob:-URL, die nach Reload ungültig ist.
        await rerenderCurrent();

        // Overlay-Shapes (Pfeile, Text, Rechtecke, …) aus JSON wiederherstellen,
        // aber ohne bgImage/type=image-Einträge (Legacy-Cleanup).
        if (loadedFabricJson) {
            try {
                const parsed = (typeof loadedFabricJson === 'string') ? JSON.parse(loadedFabricJson) : loadedFabricJson;
                if (parsed && Array.isArray(parsed.objects) && parsed.objects.length > 0) {
                    const bg = fabricCanvas.getObjects().find(o => o.bgImage === true);
                    const overlays = parsed.objects.filter(o => !o.bgImage && o.type !== 'image');
                    if (overlays.length > 0) {
                        await new Promise((res) => {
                            fabric.util.enlivenObjects(overlays, (objs) => {
                                objs.forEach(o => fabricCanvas.add(o));
                                if (bg) bg.sendToBack();
                                fabricCanvas.requestRenderAll();
                                res();
                            });
                        });
                        applyTool();
                    }
                }
            } catch (e) {
                console.warn('Fabric-JSON restore fehlgeschlagen:', e);
            }
        }
    }

    /**
     * Rendert die aktuelle Seite (image oder pdf) aus dem Buffer mit currentPageRotation.
     */
    async function rerenderCurrent() {
        if (!currentPageBuffer) return;
        if (currentPageMime.includes('pdf')) {
            await renderPdf(currentPageBuffer);
        } else if (currentPageMime.includes('image')) {
            await renderImage(currentPageBuffer, currentPageMime);
        }
    }

    /**
     * Liefert die nutzbare Breite des Canvas-Containers (minus Padding).
     * Begrenzt auf 1200px, damit auch auf großen Screens nicht alles riesig wird.
     */
    function getTargetCanvasWidth() {
        const wrap = document.querySelector('.bericht-canvas-wrap');
        if (!wrap) return 800;
        const cs = getComputedStyle(wrap);
        const padX = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight);
        const avail = wrap.clientWidth - padX - 4;
        const base = Math.max(300, Math.min(1200, Math.floor(avail)));
        return Math.round(base * currentZoom);
    }

    async function renderPdf(arrayBuffer) {
        if (!window.pdfjsLib) { console.error('PDF.js nicht geladen'); return; }
        // Buffer kopieren — pdf.js konsumiert den ArrayBuffer beim ersten Aufruf
        const pdfDoc = await pdfjsLib.getDocument({ data: arrayBuffer.slice(0) }).promise;
        const pageNum = 1;
        const page = await pdfDoc.getPage(pageNum);
        // Bei Rotation müssen wir die orientierte Breite messen, um auf den
        // Container zu passen
        const target = getTargetCanvasWidth();
        const baseViewport = page.getViewport({ scale: 1, rotation: currentPageRotation });
        const scale = target / baseViewport.width;
        const viewport = page.getViewport({ scale: scale, rotation: currentPageRotation });
        pdfCanvas.width = viewport.width;
        pdfCanvas.height = viewport.height;
        const ctx = pdfCanvas.getContext('2d');
        await page.render({ canvasContext: ctx, viewport: viewport }).promise;
        resizeFabricToCanvas();
    }

    async function renderImage(arrayBuffer, mime) {
        // Phase 6: Das Quell-Bild kommt als ZIEHBARES Fabric-Image-Objekt in
        // den Canvas, nicht mehr als festes Hintergrund-Bild. User kann es
        // verschieben, skalieren, rotieren wie jedes andere Fabric-Objekt.

        const target = getTargetCanvasWidth();
        // A4-Hochformat als Canvas-Grundfläche
        const pageAspect = 1 / 1.414; // Breite:Höhe
        const isLandscape = currentPageRotation === 90 || currentPageRotation === 270;
        const canvasW = target;
        const canvasH = isLandscape ? Math.round(target * pageAspect) : Math.round(target / pageAspect);

        // pdfCanvas wird nur noch weiße Fläche (für Fabric-Overlay-Positionierung)
        pdfCanvas.width = canvasW;
        pdfCanvas.height = canvasH;
        const ctx = pdfCanvas.getContext('2d');
        ctx.fillStyle = '#ffffff';
        ctx.fillRect(0, 0, canvasW, canvasH);

        resizeFabricToCanvas();

        // Bild als Fabric-Image laden
        const blob = new Blob([arrayBuffer], { type: mime });
        const url = URL.createObjectURL(blob);

        // Native Image-Load + fabric.Image(element) — kein CORS auf Blob-URLs,
        // bessere Fehler-Diagnose, Timeout-Fallback
        return new Promise((res) => {
            const htmlImg = new Image();
            const tid = setTimeout(() => {
                console.warn('[renderImage] Timeout beim Bild-Laden nach 10s — URL:', url);
                URL.revokeObjectURL(url);
                res();
            }, 10000);

            htmlImg.onload = () => {
                clearTimeout(tid);
                try {
                    const fabricImg = new fabric.Image(htmlImg);
                    const existing = fabricCanvas.getObjects().find(o => o.bgImage === true);
                    if (existing) fabricCanvas.remove(existing);

                    fabricImg.bgImage = true;
                    const imgRatio = Math.min(canvasW / fabricImg.width, canvasH / fabricImg.height);
                    fabricImg.scale(imgRatio);
                    fabricImg.set({
                        left: (canvasW - fabricImg.width * imgRatio) / 2,
                        top:  (canvasH - fabricImg.height * imgRatio) / 2,
                        angle: currentPageRotation,
                        selectable: true,
                        hasControls: true,
                        hasBorders: true,
                        lockRotation: false,
                    });
                    fabricCanvas.add(fabricImg);
                    fabricImg.sendToBack();
                    fabricCanvas.requestRenderAll();
                    if (typeof applyTool === 'function') applyTool();
                } catch (e) {
                    console.error('[renderImage] Fabric-Wrap fehlgeschlagen:', e);
                }
                URL.revokeObjectURL(url);
                res();
            };
            htmlImg.onerror = (err) => {
                clearTimeout(tid);
                console.error('[renderImage] htmlImg onerror:', err, 'mime:', mime, 'bufSize:', arrayBuffer.byteLength);
                URL.revokeObjectURL(url);
                res();
            };
            htmlImg.src = url;
        });
    }

    function resizeFabricToCanvas() {
        fabricCanvas.setWidth(pdfCanvas.width);
        fabricCanvas.setHeight(pdfCanvas.height);

        requestAnimationFrame(() => {
            // Fabric wickelt das Canvas in einen .canvas-container ein —
            // DIESEN müssen wir absolut über dem PDF-Canvas positionieren,
            // nicht das innere #fabric-canvas direkt.
            const fcEl = document.getElementById('fabric-canvas');
            const container = fcEl.parentElement && fcEl.parentElement.classList.contains('canvas-container')
                ? fcEl.parentElement
                : fcEl;

            const rect = pdfCanvas.getBoundingClientRect();
            const wrap = pdfCanvas.parentElement;
            const wrapRect = wrap.getBoundingClientRect();

            container.style.position = 'absolute';
            container.style.left     = (rect.left - wrapRect.left + wrap.scrollLeft) + 'px';
            container.style.top      = (rect.top  - wrapRect.top  + wrap.scrollTop)  + 'px';
            container.style.width    = pdfCanvas.clientWidth  + 'px';
            container.style.height   = pdfCanvas.clientHeight + 'px';
            container.style.zIndex   = '10';
            container.style.pointerEvents = 'auto';
        });
    }

    let loadedFabricJson = null;
    async function loadPageMeta() {
        loadedFabricJson = null;
        try {
            const r = await fetch(cfg.urls.save_annotations.replace('save_annotations', 'page_meta') + '?pageid=' + currentPageId);
            if (!r.ok) return;
            const data = await r.json();
            if (data.fabric_json) loadedFabricJson = data.fabric_json;
            if (data.note) setNoteValue(data.note);
            if (typeof data.rotation !== 'undefined' && data.rotation !== null) {
                currentPageRotation = parseInt(data.rotation, 10) || 0;
            }
            if (data.layout)      currentPageLayout = data.layout;
            if (data.image_scale) currentPageScale  = parseFloat(data.image_scale);
            if (data.image_align) currentPageAlign  = data.image_align;
            // Toolbar-Selects synchronisieren
            const lEl = document.getElementById('page-layout');
            const sEl = document.getElementById('page-imgscale');
            const aEl = document.getElementById('page-imgalign');
            if (lEl) lEl.value = currentPageLayout;
            if (sEl) sEl.value = currentPageScale.toString();
            if (aEl) aEl.value = currentPageAlign;
            document.querySelectorAll('.single-only').forEach(el => {
                el.style.display = (currentPageLayout === 'single') ? '' : 'none';
            });
        } catch (e) { /* ok */ }
    }

    /* ---------- Toolbar ---------- */
    function bindToolbar() {
        document.querySelectorAll('.tool-btn').forEach(btn => {
            btn.addEventListener('click', () => {
                document.querySelectorAll('.tool-btn.active').forEach(b => b.classList.remove('active'));
                btn.classList.add('active');
                currentTool = btn.dataset.tool;
                applyTool();
            });
        });

        document.getElementById('tool-color').addEventListener('input', e => {
            fabricCanvas.freeDrawingBrush.color = e.target.value;
            const sel = fabricCanvas.getActiveObject();
            if (sel) {
                sel.set({ stroke: e.target.value });
                if (sel.type === 'i-text' || sel.type === 'text') sel.set({ fill: e.target.value });
                fabricCanvas.requestRenderAll();
            }
        });
        document.getElementById('tool-stroke').addEventListener('input', e => {
            fabricCanvas.freeDrawingBrush.width = parseInt(e.target.value, 10);
            const sel = fabricCanvas.getActiveObject();
            if (sel) { sel.set({ strokeWidth: parseInt(e.target.value, 10) }); fabricCanvas.requestRenderAll(); }
        });

        document.getElementById('btn-undo').addEventListener('click', undo);
        document.getElementById('btn-redo').addEventListener('click', redo);
        document.getElementById('btn-delete-selected').addEventListener('click', () => {
            const sel = fabricCanvas.getActiveObjects();
            sel.forEach(o => fabricCanvas.remove(o));
            fabricCanvas.discardActiveObject();
            fabricCanvas.requestRenderAll();
        });
        // Seitenrotation
        document.getElementById('btn-rotate-left').addEventListener('click', () => rotatePage(-90));
        document.getElementById('btn-rotate-right').addEventListener('click', () => rotatePage(90));

        // Zoom
        document.getElementById('btn-zoom-in').addEventListener('click', () => setZoom(currentZoom + 0.25));
        document.getElementById('btn-zoom-out').addEventListener('click', () => setZoom(currentZoom - 0.25));
        document.getElementById('btn-zoom-reset').addEventListener('click', () => setZoom(1.0));

        // Layout / Bildgröße / Align — pro Seite
        const layoutEl = document.getElementById('page-layout');
        const scaleEl  = document.getElementById('page-imgscale');
        const alignEl  = document.getElementById('page-imgalign');
        async function savePageOptions() {
            if (!currentPageId) return;
            const fd = new FormData();
            fd.append('token', cfg.token);
            fd.append('pageid', currentPageId);
            fd.append('layout', layoutEl.value);
            fd.append('image_scale', scaleEl.value);
            fd.append('image_align', alignEl.value);
            await fetch(cfg.urls.save_page_options, { method: 'POST', body: fd });
        }
        if (layoutEl) layoutEl.addEventListener('change', async () => {
            currentPageLayout = layoutEl.value;
            document.querySelectorAll('.single-only').forEach(el => {
                el.style.display = (currentPageLayout === 'single') ? '' : 'none';
            });
            await savePageOptions();
        });
        if (scaleEl) scaleEl.addEventListener('change', async () => {
            currentPageScale = parseFloat(scaleEl.value);
            await savePageOptions();
        });
        if (alignEl) alignEl.addEventListener('change', async () => {
            currentPageAlign = alignEl.value;
            await savePageOptions();
        });

        // Schrift-Optionen für Text-Tool / selektierte Texte
        const fontFamily = document.getElementById('tool-fontfamily');
        const fontSize   = document.getElementById('tool-fontsize');
        const boldChk    = document.getElementById('tool-bold');
        const italicChk  = document.getElementById('tool-italic');

        function applyTextProps() {
            const sel = fabricCanvas.getActiveObject();
            if (sel && (sel.type === 'i-text' || sel.type === 'text' || sel.type === 'textbox')) {
                sel.set({
                    fontFamily: fontFamily.value,
                    fontSize:   parseInt(fontSize.value, 10),
                    fontWeight: boldChk.checked ? 'bold' : 'normal',
                    fontStyle:  italicChk.checked ? 'italic' : 'normal',
                });
                fabricCanvas.requestRenderAll();
            }
        }
        fontFamily.addEventListener('change', applyTextProps);
        fontSize.addEventListener('input', applyTextProps);
        boldChk.addEventListener('change', applyTextProps);
        italicChk.addEventListener('change', applyTextProps);

        // Text-Hintergrund
        const bgEl = document.getElementById('tool-bgcolor');
        const bgOff = document.getElementById('tool-bg-off');
        if (bgEl) {
            bgEl.dataset.active = 'on';
            bgEl.addEventListener('input', () => {
                bgEl.dataset.active = 'on';
                const sel = fabricCanvas.getActiveObject();
                if (sel && (sel.type === 'i-text' || sel.type === 'text' || sel.type === 'textbox')) {
                    sel.set({ textBackgroundColor: bgEl.value, padding: 6 });
                    fabricCanvas.requestRenderAll();
                }
            });
        }
        if (bgOff) {
            bgOff.addEventListener('click', () => {
                if (bgEl) bgEl.dataset.active = 'off';
                const sel = fabricCanvas.getActiveObject();
                if (sel && (sel.type === 'i-text' || sel.type === 'text' || sel.type === 'textbox')) {
                    sel.set({ textBackgroundColor: '', padding: 0 });
                    fabricCanvas.requestRenderAll();
                }
            });
        }

        // Bei Selektion eines Text-Objekts die Toolbar-Werte synchronisieren
        fabricCanvas.on('selection:created', syncTextToolbar);
        fabricCanvas.on('selection:updated', syncTextToolbar);
        function syncTextToolbar() {
            const sel = fabricCanvas.getActiveObject();
            if (!sel) return;
            if (sel.type === 'i-text' || sel.type === 'text' || sel.type === 'textbox') {
                if (sel.fontFamily) fontFamily.value = sel.fontFamily;
                if (sel.fontSize)   fontSize.value   = sel.fontSize;
                boldChk.checked   = (sel.fontWeight === 'bold');
                italicChk.checked = (sel.fontStyle === 'italic');
            }
        }
    }

    async function setZoom(z) {
        currentZoom = Math.max(0.25, Math.min(3.0, Math.round(z * 100) / 100));
        document.getElementById('zoom-label').textContent = Math.round(currentZoom * 100) + '%';
        saveSettings({ zoom: currentZoom });
        await rerenderCurrent();
    }

    async function rotatePage(deg) {
        currentPageRotation = ((currentPageRotation + deg) % 360 + 360) % 360;
        // Annotationen für die alte Orientierung sind im JSON noch da — wir werfen
        // sie vor dem Re-Render weg, damit sie nicht falsch positioniert sind.
        // (Bewusste Designentscheidung: rotieren vor dem Annotieren.)
        fabricCanvas.clear();
        await rerenderCurrent();
        // Sofort speichern, damit Rotation persistent ist
        await savePageAnnotations(false);
    }

    /* ---------- Shape-Drawing per Drag ---------- */
    let drawState = null; // { shape, startX, startY }

    function applyTool() {
        fabricCanvas.isDrawingMode = (currentTool === 'draw');
        fabricCanvas.selection = (currentTool === 'select');
        // Cursor-Hint
        fabricCanvas.defaultCursor = (currentTool === 'select') ? 'default' : 'crosshair';

        // Wenn ein Zeichen-Tool aktiv ist, sperren wir Bilder und bestehende Shapes,
        // damit sie nicht versehentlich verschoben werden. Bei 'select' wird alles
        // wieder ziehbar.
        const isSelect = (currentTool === 'select');
        fabricCanvas.getObjects().forEach(obj => {
            obj.set({
                selectable: isSelect,
                evented: isSelect,
            });
        });
        if (!isSelect) fabricCanvas.discardActiveObject();
        fabricCanvas.requestRenderAll();

        fabricCanvas.off('mouse:down', shapeDown);
        fabricCanvas.off('mouse:move', shapeMove);
        fabricCanvas.off('mouse:up',   shapeUp);

        if (['rect', 'circle', 'arrow'].includes(currentTool)) {
            fabricCanvas.on('mouse:down', shapeDown);
            fabricCanvas.on('mouse:move', shapeMove);
            fabricCanvas.on('mouse:up',   shapeUp);
        } else if (currentTool === 'text') {
            fabricCanvas.on('mouse:down', shapeDown);
        }
    }

    function shapeDown(opt) {
        // Wenn wir ein vorhandenes Objekt anklicken: nicht zeichnen, selektieren
        if (opt.target) return;
        const p = fabricCanvas.getPointer(opt.e);
        const color = document.getElementById('tool-color').value;
        const sw = parseInt(document.getElementById('tool-stroke').value, 10);

        if (currentTool === 'rect') {
            const r = new fabric.Rect({
                left: p.x, top: p.y, width: 1, height: 1,
                fill: 'transparent', stroke: color, strokeWidth: sw,
                originX: 'left', originY: 'top'
            });
            fabricCanvas.add(r);
            drawState = { shape: r, startX: p.x, startY: p.y };
        } else if (currentTool === 'circle') {
            const e = new fabric.Ellipse({
                left: p.x, top: p.y, rx: 1, ry: 1,
                fill: 'transparent', stroke: color, strokeWidth: sw,
                originX: 'left', originY: 'top'
            });
            fabricCanvas.add(e);
            drawState = { shape: e, startX: p.x, startY: p.y };
        } else if (currentTool === 'arrow') {
            const a = makeArrow(p.x, p.y, p.x + 1, p.y + 1, color, sw);
            fabricCanvas.add(a);
            drawState = { shape: a, startX: p.x, startY: p.y, isArrow: true, color, sw };
        } else if (currentTool === 'text') {
            const ff = document.getElementById('tool-fontfamily').value || 'Helvetica';
            const fs = parseInt(document.getElementById('tool-fontsize').value, 10) || 24;
            const bold = document.getElementById('tool-bold').checked;
            const ital = document.getElementById('tool-italic').checked;
            const bgEl = document.getElementById('tool-bgcolor');
            const bgActive = bgEl && bgEl.dataset.active !== 'off';
            const bgColor = bgActive ? bgEl.value : '';
            const t = new fabric.IText('Text…', {
                left: p.x, top: p.y,
                fontFamily: ff, fontSize: fs,
                fontWeight: bold ? 'bold' : 'normal',
                fontStyle:  ital ? 'italic' : 'normal',
                fill: color,
                textBackgroundColor: bgColor || '',
                padding: bgColor ? 6 : 0,
            });
            fabricCanvas.add(t);
            fabricCanvas.setActiveObject(t);
            const sb = document.querySelector('.tool-btn[data-tool="select"]');
            if (sb) sb.click();
        }
    }

    function shapeMove(opt) {
        if (!drawState) return;
        const p = fabricCanvas.getPointer(opt.e);
        const s = drawState.shape;

        if (drawState.isArrow) {
            // Alten Pfeil entfernen, neuen mit aktualisierten Endpunkten zeichnen
            fabricCanvas.remove(s);
            const newArrow = makeArrow(drawState.startX, drawState.startY, p.x, p.y, drawState.color, drawState.sw);
            fabricCanvas.add(newArrow);
            drawState.shape = newArrow;
        } else if (s.type === 'rect') {
            const w = p.x - drawState.startX;
            const h = p.y - drawState.startY;
            s.set({
                left: w < 0 ? p.x : drawState.startX,
                top:  h < 0 ? p.y : drawState.startY,
                width: Math.abs(w),
                height: Math.abs(h),
            });
        } else if (s.type === 'ellipse') {
            const w = p.x - drawState.startX;
            const h = p.y - drawState.startY;
            s.set({
                left: w < 0 ? p.x : drawState.startX,
                top:  h < 0 ? p.y : drawState.startY,
                rx: Math.abs(w) / 2,
                ry: Math.abs(h) / 2,
            });
            // Ellipse braucht ihre Größe anhand rx/ry
            s.set({ width: Math.abs(w), height: Math.abs(h) });
        }
        s.setCoords();
        fabricCanvas.requestRenderAll();
    }

    function shapeUp() {
        if (drawState && drawState.shape) {
            const s = drawState.shape;
            // Sicherstellen dass das Objekt selektierbar/drehbar ist
            s.set({ hasControls: true, hasBorders: true, lockRotation: false });
            fabricCanvas.setActiveObject(s);
        }
        drawState = null;
        // Nach dem Zeichnen automatisch auf Select wechseln
        const sb = document.querySelector('.tool-btn[data-tool="select"]');
        if (sb) sb.click();
    }

    /**
     * Baut einen Pfeil als Fabric-Group: Linie + Dreieck als Spitze.
     * Gruppe ist drehbar, skalierbar, verschiebbar.
     */
    function makeArrow(x1, y1, x2, y2, color, sw) {
        const dx = x2 - x1, dy = y2 - y1;
        const len = Math.sqrt(dx * dx + dy * dy) || 1;
        const angle = Math.atan2(dy, dx);
        const headLen = Math.max(12, sw * 4);

        // Linie als Path damit sie in die Group passt — relative Koordinaten
        const line = new fabric.Line([0, 0, len, 0], {
            stroke: color, strokeWidth: sw, originX: 'left', originY: 'center',
        });
        const head = new fabric.Triangle({
            left: len, top: 0,
            width: headLen, height: headLen,
            fill: color,
            originX: 'center', originY: 'center',
            angle: 90,
        });
        const grp = new fabric.Group([line, head], {
            left: x1, top: y1,
            originX: 'left', originY: 'center',
            angle: angle * 180 / Math.PI,
            hasControls: true, hasBorders: true, lockRotation: false,
        });
        return grp;
    }

    /* ---------- Undo/Redo (einfach) ---------- */
    const history = [];
    let histIdx = -1;
    function snapshot() {
        history.length = histIdx + 1;
        history.push(JSON.stringify(fabricCanvas.toJSON()));
        histIdx = history.length - 1;
    }
    function undo() {
        if (histIdx <= 0) return;
        histIdx--;
        fabricCanvas.loadFromJSON(history[histIdx], () => fabricCanvas.renderAll());
    }
    function redo() {
        if (histIdx >= history.length - 1) return;
        histIdx++;
        fabricCanvas.loadFromJSON(history[histIdx], () => fabricCanvas.renderAll());
    }
    setTimeout(() => {
        if (fabricCanvas) {
            fabricCanvas.on('object:added', snapshot);
            fabricCanvas.on('object:modified', snapshot);
            fabricCanvas.on('object:removed', snapshot);
        }
    }, 500);

    function rotateCurrent(deg) {
        const sel = fabricCanvas.getActiveObject();
        if (sel) {
            sel.rotate(((sel.angle || 0) + deg) % 360);
            fabricCanvas.requestRenderAll();
        }
    }

    /* ---------- Speichern ---------- */
    async function savePageAnnotations(showMessage = true) {
        if (!currentPageId) return;

        // Sicherheit: wenn kein bgImage im Canvas ist, NICHT speichern — sonst
        // überschreiben wir einen funktionierenden Bericht mit einem leeren
        // Composite
        const hasBg = fabricCanvas.getObjects().some(o => o.bgImage === true || o.type === 'image');
        if (!hasBg) {
            console.warn('[savePageAnnotations] Kein Bild im Canvas — skip, um Datenverlust zu vermeiden');
            if (showMessage) toast('Speichern übersprungen (kein Bild geladen)', 'warn');
            return;
        }

        const fd = new FormData();
        fd.append('token', cfg.token);
        fd.append('pageid', currentPageId);
        // WICHTIG: bgImage-Objekte nicht serialisieren — ihre src ist eine
        // blob:-URL, die nach Reload ungültig ist und zum weißen Canvas führt.
        // Das Quellbild wird beim Laden frisch aus page_image.php geholt.
        const jsonOut = fabricCanvas.toJSON(['bgImage']);
        if (jsonOut && Array.isArray(jsonOut.objects)) {
            jsonOut.objects = jsonOut.objects.filter(o => !o.bgImage);
        }
        fd.append('fabric_json', JSON.stringify(jsonOut));
        fd.append('note', getNoteValue() || '');
        fd.append('rotation', currentPageRotation);

        // Phase 6: Composite-PNG
        try {
            // Selektion aufheben damit keine Controls gerendert werden
            const active = fabricCanvas.getActiveObject();
            if (active) fabricCanvas.discardActiveObject();

            // Weißer Hintergrund für den Composite, damit transparente Bereiche nicht schwarz werden
            const origBg = fabricCanvas.backgroundColor;
            fabricCanvas.backgroundColor = '#ffffff';
            fabricCanvas.renderAll();

            const dataUrl = fabricCanvas.toDataURL({
                format: 'png',
                quality: 0.92,
                multiplier: 2, // 2x für bessere PDF-Qualität
            });

            // Wiederherstellen
            fabricCanvas.backgroundColor = origBg;
            if (active) fabricCanvas.setActiveObject(active);
            fabricCanvas.renderAll();

            const blob = await (await fetch(dataUrl)).blob();
            fd.append('composite', blob, 'composite.png');
        } catch (e) {
            console.warn('Composite-PNG konnte nicht erzeugt werden:', e);
        }

        const r = await fetch(cfg.urls.save_annotations, { method: 'POST', body: fd });
        const data = await r.json().catch(() => ({}));
        if (showMessage && data.success) toast('Seite gespeichert');
    }

    /* ---------- Meta-Felder Auto-Save ---------- */
    async function saveMeta() {
        const fd = new FormData();
        fd.append('token', cfg.token);
        fd.append('berichtid', cfg.berichtid);
        const titelEl = document.querySelector('input[name="titel"]');
        const tplEl   = document.querySelector('select[name="template_odt"]');
        const fmtEl   = document.getElementById('meta-format');
        const oriEl   = document.getElementById('meta-orientation');
        if (titelEl) fd.append('titel', titelEl.value);
        if (tplEl)   fd.append('template_odt', tplEl.value);
        if (fmtEl)   fd.append('page_format', fmtEl.value);
        if (oriEl)   fd.append('page_orientation', oriEl.value);
        await fetch(cfg.urls.save_meta, { method: 'POST', body: fd });
    }
    function bindMetaAutoSave() {
        ['input[name="titel"]', 'select[name="template_odt"]', '#meta-format', '#meta-orientation'].forEach(sel => {
            const el = document.querySelector(sel);
            if (!el) return;
            el.addEventListener('change', saveMeta);
        });
    }

    function bindActions() {
        bindMetaAutoSave();
        document.getElementById('btn-save-draft').addEventListener('click', async () => {
            await saveMeta();
            await savePageAnnotations(true);
        });

        // Vorschau-Modal
        const previewBtn = document.getElementById('btn-preview');
        if (previewBtn) {
            previewBtn.addEventListener('click', async () => {
                await savePageAnnotations(false);
                const url = cfg.urls.preview_pdf + '?berichtid=' + cfg.berichtid + '&t=' + Date.now();
                document.getElementById('bericht-preview-iframe').src = url;
                document.getElementById('bericht-preview-modal').style.display = 'block';
            });
        }
        const modalClose = document.getElementById('bericht-modal-close');
        if (modalClose) modalClose.addEventListener('click', closePreviewModal);
        document.querySelector('#bericht-preview-modal .bericht-modal-backdrop')
            ?.addEventListener('click', closePreviewModal);
        document.addEventListener('keydown', e => {
            if (e.key === 'Escape') closePreviewModal();
        });

        // Als Vorlage speichern
        const tplBtn = document.getElementById('btn-save-as-template');
        if (tplBtn) {
            tplBtn.addEventListener('click', async () => {
                const label = await dolPrompt('Label für die Vorlage (z. B. "PV-Anlage Standard" oder "Wallbox 11kW")');
                if (!label) return;
                await savePageAnnotations(false);
                const fd = new FormData();
                fd.append('token', cfg.token);
                fd.append('berichtid', cfg.berichtid);
                fd.append('label', label);
                const r = await fetch(cfg.urls.save_as_template, { method: 'POST', body: fd });
                const data = await r.json();
                if (data.success) toast('✓ Vorlage "' + label + '" gespeichert');
                else dolAlert('Fehler: ' + (data.error || ''));
            });
        }

        document.getElementById('btn-finalize').addEventListener('click', async () => {
            if (!(await dolConfirm('Bericht jetzt finalisieren und PDF erzeugen?'))) return;
            toast('Speichere aktuelle Seite…');
            await savePageAnnotations(false);
            toast('PDF wird erzeugt…');
            try {
                const fd = new FormData();
                fd.append('token', cfg.token);
                fd.append('berichtid', cfg.berichtid);
                const r = await fetch(cfg.urls.generate_pdf, { method: 'POST', body: fd });
                const txt = await r.text();
                let data = {};
                try { data = JSON.parse(txt); } catch (e) {
                    console.error('generate_pdf lieferte kein JSON:', txt);
                    toast('Server-Fehler (kein JSON)', 'error');
                    return;
                }
                if (data.success) {
                    toast('✓ PDF erstellt: ' + data.filename);
                    setTimeout(() => location.reload(), 1500);
                } else {
                    console.error('generate_pdf failed:', data);
                    toast('Fehler: ' + (data.error || 'unbekannt'), 'error');
                }
            } catch (e) {
                console.error('finalize exception:', e);
                toast('Netzwerk-Fehler: ' + e.message, 'error');
            }
        });
    }

    /* ---------- Anhänge & Thumbs ---------- */
    function bindAttachments() {
        const btn = document.getElementById('btn-add-selected');
        const layoutSel = document.getElementById('add-selected-layout');
        if (btn) {
            btn.addEventListener('click', async () => {
                const checks = document.querySelectorAll('.att-check:checked');
                if (!checks.length) {
                    dolAlert('Bitte zuerst Bilder ankreuzen');
                    return;
                }
                const layout = layoutSel ? layoutSel.value : 'single';

                if (layout === 'single') {
                    // Wie bisher: jedes Bild als eigene Seite
                    for (const c of checks) {
                        const fd = new FormData();
                        fd.append('token', cfg.token);
                        fd.append('berichtid', cfg.berichtid);
                        fd.append('relpath', c.dataset.relpath);
                        fd.append('mime', c.dataset.mime);
                        await fetch(cfg.urls.add_attachment, { method: 'POST', body: fd });
                        c.checked = false;
                    }
                    location.reload();
                    return;
                }

                // Grid-Layout: in Gruppen aufteilen
                const slotCount = { grid_2: 2, grid_2v: 2, grid_4: 4, grid_6: 6, before_after: 2 }[layout] || 4;
                const imageChecks = Array.from(checks).filter(c => c.dataset.mime.startsWith('image'));
                if (!imageChecks.length) {
                    dolAlert('Bitte mindestens ein Bild ankreuzen (PDFs nicht in Grids unterstützt)');
                    return;
                }
                // In Gruppen à slotCount aufteilen
                for (let i = 0; i < imageChecks.length; i += slotCount) {
                    const group = imageChecks.slice(i, i + slotCount).map(c => c.dataset.relpath);
                    const fd = new FormData();
                    fd.append('token', cfg.token);
                    fd.append('berichtid', cfg.berichtid);
                    fd.append('layout', layout);
                    fd.append('relpaths', JSON.stringify(group));
                    const r = await fetch(cfg.urls.create_grid_page, { method: 'POST', body: fd });
                    const data = await r.json().catch(() => ({}));
                    if (!data.success) {
                        dolAlert('Fehler bei Gruppe '+(Math.floor(i/slotCount)+1)+': '+(data.error || 'unbekannt'));
                        return;
                    }
                }
                location.reload();
            });
        }

        // Lösch-Buttons in der Anhänge-Liste
        document.querySelectorAll('.att-delete').forEach(btn => {
            btn.addEventListener('click', async (e) => {
                e.preventDefault();
                e.stopPropagation();
                const rel = btn.dataset.relpath;
                const ref = btn.dataset.sourceRef || '';
                const name = btn.parentElement.querySelector('.att-name')?.textContent || rel;
                if (!(await dolConfirm('Datei "' + name + '" aus ' + (ref ? ref : 'dem Anhang') + ' wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.'))) return;
                const fd = new FormData();
                fd.append('token', cfg.token);
                fd.append('relpath', rel);
                const r = await fetch(cfg.urls.delete_attachment, { method: 'POST', body: fd });
                const data = await r.json().catch(() => ({}));
                if (data.success) {
                    btn.parentElement.remove();
                    toast('Datei gelöscht');
                } else {
                    dolAlert('Löschen fehlgeschlagen: ' + (data.error || 'unbekannt'));
                }
            });
        });
    }

    function bindExtraUpload() {
        const inp = document.getElementById('bericht-extra-upload');
        if (inp) {
            inp.addEventListener('change', async () => {
                if (!inp.files.length) return;
                const fd = new FormData();
                fd.append('token', cfg.token);
                fd.append('berichtid', cfg.berichtid);
                fd.append('file', inp.files[0]);
                const r = await fetch(cfg.urls.upload_extra, { method: 'POST', body: fd });
                const data = await r.json();
                if (data.success) location.reload();
                else dolAlert('Upload fehlgeschlagen: ' + (data.error || ''));
            });
        }

        // QR-Modal für Mobile-Upload
        const qrBtn = document.getElementById('btn-show-qr');
        if (qrBtn) qrBtn.addEventListener('click', openQrModal);
        const qrClose = document.getElementById('bericht-qr-close');
        if (qrClose) qrClose.addEventListener('click', closeQrModal);
        document.querySelector('#bericht-qr-modal .bericht-modal-backdrop')
            ?.addEventListener('click', closeQrModal);
    }

    let qrPollInterval = null;
    let qrLastPageCount = null;

    async function openQrModal() {
        const fd = new FormData();
        fd.append('token', cfg.token);
        fd.append('element_id', cfg.element_id);
        fd.append('element_type', cfg.element_type);
        const r = await fetch(cfg.urls.create_upload_token, { method: 'POST', body: fd });
        const data = await r.json();
        if (!data.success) {
            dolAlert('Token-Erstellung fehlgeschlagen: ' + (data.error || ''));
            return;
        }
        const url = data.url;
        const qrContainer = document.getElementById('qr-code-container');
        qrContainer.innerHTML = '';
        if (typeof QRCode !== 'undefined') {
            new QRCode(qrContainer, {
                text: url,
                width: 280,
                height: 280,
                colorDark: '#000',
                colorLight: '#fff',
                correctLevel: QRCode.CorrectLevel.M,
            });
        } else {
            qrContainer.textContent = url;
        }
        document.getElementById('qr-validity').textContent = data.expires_in_min;
        const linkEl = document.getElementById('qr-url-link');
        linkEl.href = url;
        linkEl.textContent = url.length > 60 ? url.substring(0, 57) + '...' : url;

        document.getElementById('bericht-qr-modal').style.display = 'block';

        // Polling alle 5 Sek: Fotos landen jetzt im Auftragsordner, nicht als Pages.
        // Prüfe ob sich die Anhänge-Anzahl ändert → Seite neu laden für Anhänge-Browser-Refresh.
        qrLastPageCount = document.querySelectorAll('.attachment-item').length;
        if (qrPollInterval) clearInterval(qrPollInterval);
        qrPollInterval = setInterval(async () => {
            try {
                const r = await fetch(cfg.urls.list_pages + '?berichtid=' + cfg.berichtid);
                const d = await r.json();
                // Fallback: auch auf Pages prüfen (bestehende Flows), primär aber Anhänge-Änderung
                const currentAttachments = document.querySelectorAll('.attachment-item').length;
                if (currentAttachments !== qrLastPageCount || (d.success && d.count !== document.querySelectorAll('.page-thumb').length)) {
                    document.querySelector('.qr-status').textContent =
                        '✓ Neue Fotos hochgeladen — Seite wird neu geladen…';
                    setTimeout(() => location.reload(), 1500);
                    clearInterval(qrPollInterval);
                }
            } catch (e) {}
        }, 5000);
    }

    function closeQrModal() {
        const m = document.getElementById('bericht-qr-modal');
        if (m) m.style.display = 'none';
        if (qrPollInterval) { clearInterval(qrPollInterval); qrPollInterval = null; }
    }

    function bindThumbs() {
        document.querySelectorAll('.page-thumb').forEach(t => {
            t.addEventListener('click', e => {
                if (e.target.closest('.thumb-del')) return;
                loadPage(t);
            });
            const del = t.querySelector('.thumb-del');
            if (del) del.addEventListener('click', async (e) => {
                e.stopPropagation();
                if (!(await dolConfirm(cfg.lang.confirm_del))) return;
                const fd = new FormData();
                fd.append('token', cfg.token);
                fd.append('pageid', t.dataset.pageid);
                await fetch(cfg.urls.delete_page, { method: 'POST', body: fd });
                location.reload();
            });
        });

        // Hell/Dunkel-Toggle
        const tg = document.getElementById('btn-toggle-thumb-bg');
        if (tg) tg.addEventListener('click', () => {
            const list = document.getElementById('bericht-page-list');
            list.classList.toggle('paper-light');
            list.classList.toggle('paper-dark');
        });

        // Unterschriften-Verifikation
        document.querySelectorAll('.thumb-verify').forEach(btn => {
            btn.addEventListener('click', async (e) => {
                e.stopPropagation();
                const pageid = btn.dataset.pageid;
                btn.textContent = '⏳';
                try {
                    const fd = new FormData();
                    fd.append('token', cfg.token);
                    fd.append('pageid', pageid);
                    const r = await fetch(cfg.urls.verify_signature, { method: 'POST', body: fd });
                    const data = await r.json();
                    btn.textContent = '🔍';
                    if (!data.success) {
                        dolAlert('Fehler: ' + (data.error || 'unbekannt'));
                        return;
                    }
                    showSignatureVerifyResult(data);
                } catch (err) {
                    btn.textContent = '🔍';
                    dolAlert('Netzwerkfehler: ' + err.message);
                }
            });
        });
    }

    /**
     * Zeigt das Ergebnis der Unterschriften-Verifikation in einem Modal.
     */
    function showSignatureVerifyResult(data) {
        const verified = data.verified;
        const m = data.meta || {};
        const icon = verified ? '✅' : '⚠️';
        const title = verified ? 'Unterschrift verifiziert' : 'Unterschrift NICHT verifiziert';
        const bg = verified ? '#5cb85c' : '#d9534f';

        const html = `
            <div class="bericht-modal" id="sig-verify-modal" style="display:block;">
              <div class="bericht-modal-backdrop"></div>
              <div class="bericht-modal-content" style="max-width:600px;left:50%;transform:translateX(-50%);height:auto;bottom:auto;top:10vh;">
                <div class="bericht-modal-header" style="background:${bg};color:#fff;">
                  <h3 style="color:#fff;">${icon} ${escapeHtml(title)}</h3>
                  <button type="button" id="sig-verify-close" title="Schließen">✕</button>
                </div>
                <div class="bericht-modal-body" style="padding:20px;overflow-y:auto;">
                  ${!verified ? `<p style="color:#d9534f;font-weight:600;">${escapeHtml(data.reason || '')}</p>` : ''}
                  <table style="width:100%;font-size:13px;">
                    <tr><td style="opacity:0.7;padding:4px 0;">Unterzeichner:</td><td><strong>${escapeHtml(m.signer_name || '—')}</strong></td></tr>
                    <tr><td style="opacity:0.7;padding:4px 0;">Kunde:</td><td>${escapeHtml(m.kunde || '')}</td></tr>
                    <tr><td style="opacity:0.7;padding:4px 0;">Parent:</td><td>${escapeHtml(m.parent_ref || '')}</td></tr>
                    <tr><td style="opacity:0.7;padding:4px 0;">Signiert am:</td><td>${escapeHtml(m.signed_at || '')}</td></tr>
                    <tr><td style="opacity:0.7;padding:4px 0;">Erfasst durch:</td><td>${escapeHtml(m.user_login || '')}</td></tr>
                    ${m.gps_lat ? `<tr><td style="opacity:0.7;padding:4px 0;">GPS:</td><td><a href="https://www.openstreetmap.org/?mlat=${m.gps_lat}&mlon=${m.gps_lon}&zoom=18" target="_blank">${m.gps_lat}, ${m.gps_lon}</a></td></tr>` : ''}
                    <tr><td style="opacity:0.7;padding:4px 0;">IP:</td><td><code>${escapeHtml(m.remote_ip || '')}</code></td></tr>
                    <tr><td style="opacity:0.7;padding:4px 0;vertical-align:top;">Gespeicherter Hash:</td><td><code style="font-size:10px;word-break:break-all;">${escapeHtml(data.stored_hash || '')}</code></td></tr>
                    <tr><td style="opacity:0.7;padding:4px 0;vertical-align:top;">Aktueller Hash:</td><td><code style="font-size:10px;word-break:break-all;">${escapeHtml(data.current_hash || '')}</code></td></tr>
                    <tr><td style="opacity:0.7;padding:4px 0;">Seiten bei Signatur:</td><td>${data.expected_page_count}</td></tr>
                    <tr><td style="opacity:0.7;padding:4px 0;">Seiten jetzt vor Signatur:</td><td>${data.current_page_count}</td></tr>
                  </table>
                </div>
              </div>
            </div>
        `;
        const wrapper = document.createElement('div');
        wrapper.innerHTML = html;
        document.body.appendChild(wrapper.firstElementChild);
        document.getElementById('sig-verify-close').onclick = () => {
            document.getElementById('sig-verify-modal').remove();
        };
    }

    function escapeHtml(s) {
        return String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
    }

    /**
     * Rendert alle Thumbnails in der rechten Seitenleiste.
     * Holt jedes Bild über page_image.php und zeichnet es klein in das Thumb-Canvas.
     * PDFs werden mit PDF.js gerendert.
     */
    async function renderAllThumbs() {
        const thumbs = document.querySelectorAll('.page-thumb');
        for (const t of thumbs) {
            const pageid = t.dataset.pageid;
            const canvas = t.querySelector('.thumb-canvas');
            if (!canvas || !pageid) continue;
            try {
                const r = await fetch(cfg.urls.page_image + '?pageid=' + pageid);
                const ct = r.headers.get('Content-Type') || '';
                const buf = await r.arrayBuffer();
                if (ct.includes('pdf')) {
                    await renderThumbPdf(canvas, buf);
                } else if (ct.includes('image')) {
                    await renderThumbImage(canvas, buf, ct);
                }
            } catch (e) { /* skip */ }
        }
    }

    async function renderThumbImage(canvas, buf, mime) {
        return new Promise(res => {
            const blob = new Blob([buf], { type: mime });
            const url = URL.createObjectURL(blob);
            const img = new Image();
            img.onload = () => {
                const maxSide = 200;
                const ratio = Math.min(maxSide / img.width, maxSide / img.height);
                canvas.width  = Math.round(img.width  * ratio);
                canvas.height = Math.round(img.height * ratio);
                const ctx = canvas.getContext('2d');
                ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
                URL.revokeObjectURL(url);
                res();
            };
            img.onerror = () => { URL.revokeObjectURL(url); res(); };
            img.src = url;
        });
    }

    async function renderThumbPdf(canvas, buf) {
        if (!window.pdfjsLib) return;
        try {
            const doc = await pdfjsLib.getDocument({ data: buf.slice(0) }).promise;
            const page = await doc.getPage(1);
            const base = page.getViewport({ scale: 1 });
            const scale = 200 / base.width;
            const vp = page.getViewport({ scale: scale });
            canvas.width  = vp.width;
            canvas.height = vp.height;
            await page.render({ canvasContext: canvas.getContext('2d'), viewport: vp }).promise;
        } catch (e) { /* skip */ }
    }

    function bindSortable() {
        const list = document.getElementById('bericht-page-list');
        if (!list || !window.Sortable) return;
        Sortable.create(list, {
            animation: 150,
            onEnd: async () => {
                const ids = Array.from(list.querySelectorAll('.page-thumb')).map(t => t.dataset.pageid);
                const fd = new FormData();
                fd.append('token', cfg.token);
                fd.append('order', JSON.stringify(ids));
                await fetch(cfg.urls.reorder_pages, { method: 'POST', body: fd });
            }
        });
    }

    function closePreviewModal() {
        const m = document.getElementById('bericht-preview-modal');
        if (m) m.style.display = 'none';
        const ifr = document.getElementById('bericht-preview-iframe');
        if (ifr) ifr.src = 'about:blank';
    }

    /* ---------- Helpers ---------- */
    function toast(msg) {
        const t = document.createElement('div');
        t.className = 'bericht-toast';
        t.textContent = msg;
        document.body.appendChild(t);
        setTimeout(() => t.remove(), 2000);
    }

    /* ---------- Dolibarr-Style Modal-Dialoge (ersetzen alert/confirm/prompt) ---------- */
    function dolModal(opts) {
        // opts: { title, body, buttons:[{label, value, primary}], input:boolean, defaultValue }
        return new Promise((resolve) => {
            const ov = document.createElement('div');
            ov.className = 'bericht-dolmodal-overlay';
            const box = document.createElement('div');
            box.className = 'bericht-dolmodal';
            const h = document.createElement('div');
            h.className = 'bericht-dolmodal-title';
            h.textContent = opts.title || 'Bericht';
            const b = document.createElement('div');
            b.className = 'bericht-dolmodal-body';
            b.textContent = opts.body || '';
            let inputEl = null;
            if (opts.input) {
                inputEl = document.createElement('input');
                inputEl.type = 'text';
                inputEl.className = 'bericht-dolmodal-input';
                inputEl.value = opts.defaultValue || '';
                b.appendChild(document.createElement('br'));
                b.appendChild(inputEl);
            }
            const foot = document.createElement('div');
            foot.className = 'bericht-dolmodal-foot';
            (opts.buttons || [{label:'OK', value:true, primary:true}]).forEach(btn => {
                const el = document.createElement('button');
                el.type = 'button';
                el.className = btn.primary ? 'butAction butActionConfirm' : 'butAction';
                el.textContent = btn.label;
                el.addEventListener('click', () => {
                    const val = opts.input ? (btn.value ? (inputEl.value || null) : null) : btn.value;
                    ov.remove();
                    resolve(val);
                });
                foot.appendChild(el);
            });
            box.appendChild(h); box.appendChild(b); box.appendChild(foot);
            ov.appendChild(box);
            document.body.appendChild(ov);
            if (inputEl) { inputEl.focus(); inputEl.select(); }
            ov.addEventListener('click', (e) => {
                if (e.target === ov) { ov.remove(); resolve(opts.input ? null : false); }
            });
        });
    }
    async function dolAlert(msg, title) {
        return dolModal({
            title: title || 'Hinweis',
            body: msg,
            buttons: [{label:'OK', value:true, primary:true}],
        });
    }
    async function dolConfirm(msg, title) {
        return dolModal({
            title: title || 'Bestätigen',
            body: msg,
            buttons: [
                {label:'Abbrechen', value:false},
                {label:'OK', value:true, primary:true},
            ],
        });
    }
    async function dolPrompt(msg, defaultValue, title) {
        return dolModal({
            title: title || 'Eingabe',
            body: msg,
            input: true,
            defaultValue: defaultValue || '',
            buttons: [
                {label:'Abbrechen', value:false},
                {label:'OK', value:true, primary:true},
            ],
        });
    }
    // Globaler Handler: Links/Buttons mit data-dolconfirm abfangen
    document.addEventListener('click', async (e) => {
        const el = e.target.closest('[data-dolconfirm]');
        if (!el) return;
        if (el.dataset._dolconfirmed === '1') return; // schon durchgelaufen
        e.preventDefault();
        e.stopPropagation();
        const ok = await dolConfirm(el.getAttribute('data-dolconfirm'));
        if (!ok) return;
        el.dataset._dolconfirmed = '1';
        if (el.tagName === 'A') {
            window.location.href = el.getAttribute('href');
        } else {
            el.click();
        }
    }, true);

    // Dolibarr injiziert eine globale leere jQuery-UI-Confirm-Box (#confirm-dialog-box
     // und #confirm-dialog-box-<btnid>) — die hat bei uns nie Inhalt weil wir eigene
     // Modals nutzen. Kille sie beim Laden und beobachte weitere Einfügungen.
    function killDolibarrConfirmBoxes() {
        document.querySelectorAll('[id^="confirm-dialog-box"]').forEach(el => {
            const wrap = el.closest('.ui-dialog') || el;
            wrap.remove();
        });
    }
    new MutationObserver(killDolibarrConfirmBoxes)
        .observe(document.documentElement, { childList: true, subtree: true });
    document.addEventListener('DOMContentLoaded', () => { killDolibarrConfirmBoxes(); init(); });
})();
